课程内容提交

This commit is contained in:
LSGOMYP
2020-08-16 00:12:42 +08:00
parent 8ba131054f
commit 6372e08a3a
9 changed files with 3786 additions and 0 deletions

View File

@@ -0,0 +1,663 @@
# 双指针技术在求解算法题中的应用
## 1 C# 和 Python 中的链表结构
Python `list` 的源码地址:
https://github.com/python/cpython/blob/master/Include/listobject.h
https://github.com/python/cpython/blob/master/Objects/listobject.c
C# `List<T>` 的源码地址:
https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,cf7f4095e4de7646
通过阅读源码,我们发现 Python 的 `list` 与 C# 的 `List<T>` 一致都是通过动态数组的方式来实现的。
Python 的内置结构中没有链表这种结构而C# 的内置结构中封装了双向链表 `LinkedList<T>`,内部结点为 `LinkedListNode<T>`,源码地址如下:
https://referencesource.microsoft.com/#System/compmod/system/collections/generic/linkedlist.cs,df5a6c7b6b60da4f
**LinkedListNode<T>**
- `public LinkedListNode<T> Next { get; }` -> 获取下一个节点
- `public LinkedListNode<T> Previous { get; }` -> 获取上一个节点
- `public T Value { get; set; }` -> 获取或设置包含在节点中的值。
**LinkedList<T>**
- `public LinkedListNode<T> AddFirst(T value);` -> 添加包含指定的值的开头的新节点
- `public LinkedListNode<T> AddLast(T value);` -> 添加包含指定的值的末尾的新节点
- `public LinkedListNode<T> AddBefore(LinkedListNode<T> node, T value);` -> 添加包含在指定的现有节点前的指定的值的新节点
- `public LinkedListNode<T> AddAfter(LinkedListNode<T> node, T value);` -> 添加包含指定的值中指定的现有节点后的新节点
- `public void AddFirst(LinkedListNode<T> node);` -> 将指定的新节点添加的开头
- `public void AddLast(LinkedListNode<T> node);` -> 将指定的新节点添加的末尾
- `public void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode);` -> 在指定的现有节点之前添加指定的新节点
- `public void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode);` -> 在指定的现有节点之后添加指定的新节点
- `public bool Remove(T value);` -> 移除从指定的值的第一个匹配项
- `public void Remove(LinkedListNode<T> node);` -> 移除指定的节点
- `public void RemoveFirst();` -> 删除的开始处的节点
- `public void RemoveLast();` -> 删除节点的末尾
- `public LinkedListNode<T> Find(T value);` -> 查找包含指定的值的第一个节点。
- `public LinkedListNode<T> FindLast(T value);` -> 查找包含指定的值的最后一个节点。
- `public void Clear();` -> 删除所有节点
- `public int Count { get; }` -> 获取中实际包含的节点数
- `public LinkedListNode<T> First { get; }` -> 获取的第一个节点
- `public LinkedListNode<T> Last { get; }` -> 获取的最后一个节点
```c
public static void LinkedListSample()
{
LinkedList<int> lst = new LinkedList<int>();
lst.AddFirst(3);
lst.AddLast(1);
lst.AddLast(4);
foreach (int item in lst)
{
Console.Write(item+" ");
}
Console.WriteLine();
LinkedListNode<int> cur = lst.Find(3);
lst.AddBefore(cur, 2);
foreach (int item in lst)
{
Console.Write(item + " ");
}
Console.WriteLine();
lst.Remove(3);
foreach (int item in lst)
{
Console.Write(item + " ");
}
Console.WriteLine();
lst.Clear();
}
// 3 1 4
// 2 3 1 4
// 2 1 4
```
## 2 反转链表
> - 题号206
> - 难度:简单
> - https://leetcode-cn.com/problems/reverse-linked-list/
反转一个单链表。
<b>示例</b>:
```c
: 1->2->3->4->5->NULL
: 5->4->3->2->1->NULL
```
<b>进阶</b>:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
**思路:利用双指针的方式**
`p1`作为前面的指针探路,`p2`作为后面的指针跟进,顺着链表跑一圈,搞定问题。
**C# 语言**
- 状态:通过
- 27 / 27 个通过测试用例
- 执行用时: 116 ms, 在所有 C# 提交中击败了 97.50% 的用户
- 内存消耗: 23.3 MB, 在所有 C# 提交中击败了 5.26% 的用户
```c
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int x) { val = x; }
* }
*/
public class Solution
{
public ListNode ReverseList(ListNode head)
{
if (head == null || head.next == null)
return head;
ListNode p1 = head;
ListNode p2 = null;
while (p1 != null)
{
ListNode temp = p1.next;
p1.next = p2;
p2 = p1;
p1 = temp;
}
return p2;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时36 ms, 在所有 Python3 提交中击败了 92.27% 的用户
- 内存消耗14.6 MB, 在所有 Python3 提交中击败了 17.65% 的用户
```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if head is None or head.next is None:
return head
p1 = head
p2 = None
while p1 is not None:
temp = p1.next
p1.next = p2
p2 = p1
p1 = temp
return p2
```
## 3 删除链表的倒数第N个节点
> - 题号19
> - 难度:中等
> - https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
给定一个链表,删除链表的倒数第`n`个节点,并且返回链表的头结点。
**示例**
```c
: 1->2->3->4->5, n = 2.
1->2->3->5.
```
**说明**
给定的`n`保证是有效的。
**进阶**
你能尝试使用一趟扫描实现吗?
**思路:利用双指针的方式**
使用两个指针,前面的指针`p1`先走`n`步,接着让后面的指针`p2``p1`同步走,`p1`走到终点,`p2`即走到要移除的结点位置。
**C# 语言**
- 执行结果:通过
- 执行用时104 ms, 在所有 C# 提交中击败了 86.93% 的用户
- 内存消耗24.6 MB, 在所有 C# 提交中击败了 100.00% 的用户
```c
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int x) { val = x; }
* }
*/
public class Solution
{
public ListNode RemoveNthFromEnd(ListNode head, int n)
{
ListNode p1 = head;
ListNode p2 = head;
while (n > 0)
{
p1 = p1.next;
n--;
}
if (p1 == null) //移除头结点
{
return head.next;
}
while (p1.next != null)
{
p1 = p1.next;
p2 = p2.next;
}
p2.next = p2.next.next;
return head;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时48 ms, 在所有 Python3 提交中击败了 23.58% 的用户
- 内存消耗13.5 MB, 在所有 Python3 提交中击败了 7.83% 的用户
```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
p2 = head
p1 = head
while (n > 0):
p1 = p1.next
n -= 1
if (p1 is None): # 移除头结点
return head.next
while (p1.next):
p2 = p2.next
p1 = p1.next
p2.next = p2.next.next
return head
```
## 4 删除排序链表中的重复元素
> - 题号83
> - 难度:简单
> - https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
**示例 1:**
```c
: 1->1->2
: 1->2
```
**示例 2:**
```c
: 1->1->2->3->3
: 1->2->3
```
**思路:利用双指针的方式**
`p1`作为前面的指针探路,`p2`作为后面的指针跟进,如果遇到重复元素,`p2.next`跳过去,`p1`跑完整个链表所有重复元素都被摘下来。
**C# 语言**
- 执行结果:通过
- 执行用时160 ms, 在所有 C# 提交中击败了 5.23% 的用户
- 内存消耗25.9 MB, 在所有 C# 提交中击败了 5.72% 的用户
```c
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int x) { val = x; }
* }
*/
public class Solution
{
public ListNode DeleteDuplicates(ListNode head)
{
if (head == null)
return head;
ListNode p1 = head.next;
ListNode p2 = head;
while (p1 != null)
{
if (p1.val == p2.val)
p2.next = p1.next;
else
p2 = p2.next;
p1 = p1.next;
}
return head;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时52 ms, 在所有 Python3 提交中击败了 33.88% 的用户
- 内存消耗13.5 MB, 在所有 Python3 提交中击败了 12.75% 的用户
```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
if head is None:
return head
p1 = head.next
p2 = head
while p1 is not None:
if p1.val == p2.val:
p2.next = p1.next
else:
p2 = p2.next
p1 = p1.next
return head
```
## 5 环形链表
> - 题号141
> - 难度:简单
> - https://leetcode-cn.com/problems/linked-list-cycle/
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数`pos` 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果`pos`是 -1则在该链表中没有环。
<b>示例 1</b>
```c
head = [3,2,0,-4], pos = 1
true
```
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9hc3NldHMubGVldGNvZGUtY24uY29tL2FsaXl1bi1sYy11cGxvYWQvdXBsb2Fkcy8yMDE4LzEyLzA3L2NpcmN1bGFybGlua2VkbGlzdC5wbmc)
<b>示例 2</b>
```c
head = [1,2], pos = 0
true
```
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9hc3NldHMubGVldGNvZGUtY24uY29tL2FsaXl1bi1sYy11cGxvYWQvdXBsb2Fkcy8yMDE4LzEyLzA3L2NpcmN1bGFybGlua2VkbGlzdF90ZXN0Mi5wbmc)
<b>示例 3</b>
```c
head = [1], pos = -1
false
```
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9hc3NldHMubGVldGNvZGUtY24uY29tL2FsaXl1bi1sYy11cGxvYWQvdXBsb2Fkcy8yMDE4LzEyLzA3L2NpcmN1bGFybGlua2VkbGlzdF90ZXN0My5wbmc)
<b>进阶</b>
你能用 O(1)(即,常量)内存解决此问题吗?
**思路:利用双指针的方式**
通常情况下,判断是否包含了重复的元素,我们使用`Hash`的方式来做。对于单链表的这种场景,我们也可以使用双指针的方式。
第一个指针 `p1` 每次移动两个节点,第二个指针 `p2` 每次移动一个节点,如果该链表存在环的话,第一个指针一定会再次碰到第二个指针,反之,则不存在环。
比如:`head = [1,2,3,4,5]`,奇数
```c
p11 3 5 2 4 1
p21 2 3 4 5 1
```
比如:`head = [1,2,3,4]`,偶数
```c
p11 3 1 3 1
p21 2 3 4 1
```
**C# 语言**
- 状态:通过
- 执行用时: 112 ms, 在所有 C# 提交中击败了 98.43% 的用户
- 内存消耗: 24.9 MB, 在所有 C# 提交中击败了 5.13% 的用户
```c
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public bool HasCycle(ListNode head) {
ListNode p1 = head;
ListNode p2 = head;
while (p1 != null && p1.next != null)
{
p1 = p1.next.next;
p2 = p2.next;
if (p1 == p2)
return true;
}
return false;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时56 ms, 在所有 Python3 提交中击败了 60.97% 的用户
- 内存消耗16.6 MB, 在所有 Python3 提交中击败了 11.81% 的用户
```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
p1 = head
p2 = head
while p1 is not None and p1.next is not None:
p1 = p1.next.next
p2 = p2.next
if p1 == p2:
return True
return False
```
## 6 排序链表
> - 题号148
> - 难度:中等
> - https://leetcode-cn.com/problems/sort-list/
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
<b>示例 1:</b>
```c
: 4->2->1->3
: 1->2->3->4
```
<b>示例 2:</b>
```c
: -1->5->3->4->0
: -1->0->3->4->5
```
**思路:模仿并归排序的思路,典型的回溯算法。**
如果待排的元素存储在数组中,我们可以用并归排序。而这些元素存储在链表中,我们无法直接利用并归排序,只能借鉴并归排序的思想对算法进行修改。
并归排序的思想是将待排序列进行分组,直到包含一个元素为止,然后回溯合并两个有序序列,最后得到排序序列。
对于链表我们可以递归地将当前链表分为两段然后merge分两段的方法是使用双指针法`p1`指针每次走两步,`p2`指针每次走一步,直到`p1`走到末尾,这时`p2`所在位置就是中间位置,这样就分成了两段。
**C# 语言**
- 状态:通过
- 16 / 16 个通过测试用例
- 执行用时: 124 ms, 在所有 C# 提交中击败了 100.00% 的用户
- 内存消耗: 29 MB, 在所有 C# 提交中击败了 25.00% 的用户
```c
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int x) { val = x; }
* }
*/
public class Solution
{
public ListNode SortList(ListNode head)
{
if (head == null)
return null;
return MergeSort(head);
}
private ListNode MergeSort(ListNode node)
{
if (node.next == null)
{
return node;
}
ListNode p1 = node;
ListNode p2 = node;
ListNode cut = null;
while (p1 != null && p1.next != null)
{
cut = p2;
p2 = p2.next;
p1 = p1.next.next;
}
cut.next = null;
ListNode l1 = MergeSort(node);
ListNode l2 = MergeSort(p2);
return MergeTwoLists(l1, l2);
}
private ListNode MergeTwoLists(ListNode l1, ListNode l2)
{
ListNode pHead = new ListNode(-1);
ListNode temp = pHead;
while (l1 != null && l2 != null)
{
if (l1.val < l2.val)
{
temp.next = l1;
l1 = l1.next;
}
else
{
temp.next = l2;
l2 = l2.next;
}
temp = temp.next;
}
if (l1 != null)
temp.next = l1;
if (l2 != null)
temp.next = l2;
return pHead.next;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时216 ms, 在所有 Python3 提交中击败了 75.99% 的用户
- 内存消耗20.7 MB, 在所有 Python3 提交中击败了 28.57% 的用户
```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def sortList(self, head: ListNode) -> ListNode:
if head is None:
return head
return self.mergeSort(head)
def mergeSort(self, node: ListNode) -> ListNode:
if node.next is None:
return node
p1 = node
p2 = node
cute = None
while p1 is not None and p1.next is not None:
cute = p2
p2 = p2.next
p1 = p1.next.next
cute.next = None
l1 = self.mergeSort(node)
l2 = self.mergeSort(p2)
return self.mergeTwoLists(l1, l2)
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
pHead = ListNode(-1)
temp = pHead
while l1 is not None and l2 is not None:
if l1.val < l2.val:
temp.next = l1
l1 = l1.next
else:
temp.next = l2
l2 = l2.next
temp = temp.next
if l1 is not None:
temp.next = l1
if l2 is not None:
temp.next = l2
return pHead.next
```

View File

@@ -0,0 +1,950 @@
# 集合技术在求解算法题中的应用
## 1 C# 和 Python 中的集合结构
<u>集合技术在解题中主要用于处理有数据重复出现的问题</u>。
**HashSet<T>**
C# 语言中 `HashSet<T>` 是包含不重复项的无序列表,称为“集合(`set`)”。由于`set`是一个保留字,所以用`HashSet`来表示。
源码:
https://referencesource.microsoft.com/#System.Core/System/Collections/Generic/HashSet.cs,2d265edc718b158b
<u>HashSet的成员方法</u>
- `public HashSet(); ` -> 构造函数
- `public HashSet(IEnumerable<T> collection);` -> 构造函数
- `public int Count { get; }` -> 获取集合中包含的元素数。
- `public bool Add(T item);` -> 将指定的元素添加到集合中。
- `public bool Remove(T item);` -> 从集合中移除指定元素。
- `public void Clear();` -> 从集合中移除所有元素。
- `public bool Contains(T item);` -> 确定集合中是否包含指定的元素。
- `public void UnionWith(IEnumerable<T> other);` -> 并集
- `public void IntersectWith(IEnumerable<T> other);` -> 交集
- `public void ExceptWith(IEnumerable<T> other);` -> 差集
- `public bool IsSubsetOf(IEnumerable<T> other);` -> 确定当前集合是否为指定集合的子集。
- `public bool IsProperSubsetOf(IEnumerable<T> other);` -> 确定当前集合是否为指定集合的真子集。
- `public bool IsSupersetOf(IEnumerable<T> other);` -> 确定当前集合是否为指定集合的超集。
- `public bool IsProperSupersetOf(IEnumerable<T> other);` -> 确定当前集合是否为指定集合的真超集。
- `public bool Overlaps(IEnumerable<T> other);` -> 确定是否当前集合和指定的集合共享通用元素。
- `public bool SetEquals(IEnumerable<T> other);` -> 确定是否当前集合和指定集合包含相同的元素。
**set**
Python 中`set``dict`类似,也是一组`key`的集合,但不存储`value`。由于`key`不能重复,所以,在`set`中,没有重复的`key`
注意,`key`为不可变类型,即可哈希的值。
```python
num = {}
print(type(num)) # <class 'dict'>
num = {1, 2, 3, 4}
print(type(num)) # <class 'set'>
```
<u>集合的创建</u>
- 先创建对象再加入元素。
- 在创建空集合的时候只能使用`s = set()`,因为`s = {}`创建的是空字典。
```python
basket = set()
basket.add('apple')
basket.add('banana')
print(basket) # {'banana', 'apple'}
```
- 直接把一堆元素用花括号括起来`{元素1, 元素2, ..., 元素n}`
- 重复元素在`set`中会被自动被过滤。
```python
basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
print(basket) # {'banana', 'apple', 'pear', 'orange'}
```
- 使用`set(value)`工厂函数,把列表或元组转换成集合。
```python
a = set('abracadabra')
print(a)
# {'r', 'b', 'd', 'c', 'a'}
b = set(("Google", "Lsgogroup", "Taobao", "Taobao"))
print(b)
# {'Taobao', 'Lsgogroup', 'Google'}
c = set(["Google", "Lsgogroup", "Taobao", "Google"])
print(c)
# {'Taobao', 'Lsgogroup', 'Google'}
```
- 去掉列表中重复的元素
```python
lst = [0, 1, 2, 3, 4, 5, 5, 3, 1]
temp = []
for item in lst:
if item not in temp:
temp.append(item)
print(temp) # [0, 1, 2, 3, 4, 5]
a = set(lst)
print(list(a)) # [0, 1, 2, 3, 4, 5]
```
从结果发现集合的两个特点:无序 (unordered) 和唯一 (unique)。
由于 `set` 存储的是无序集合,所以我们不可以为集合创建索引或执行切片(slice)操作,也没有键(keys)可用来获取集合中元素的值,但是可以判断一个元素是否在集合中。
<u>访问集合中的值</u>
- 可以使用`len()`內建函数得到集合的大小。
```python
thisset = set(['Google', 'Baidu', 'Taobao'])
print(len(thisset)) # 3
```
- 可以使用`for`把集合中的数据一个个读取出来。
```python
thisset = set(['Google', 'Baidu', 'Taobao'])
for item in thisset:
print(item)
# Baidu
# Google
# Taobao
```
- 可以通过`in``not in`判断一个元素是否在集合中已经存在
```python
thisset = set(['Google', 'Baidu', 'Taobao'])
print('Taobao' in thisset) # True
print('Facebook' not in thisset) # True
```
<u>集合的内置方法</u>
- `set.add(elmnt)` -> 给集合添加元素,如果添加的元素在集合中已存在,则不执行任何操作。
- `set.update(set)` -> 修改当前集合,可以添加新的元素或集合到当前集合中,如果添加的元素在集合中已存在,则该元素只会出现一次,重复的会忽略。
- `set.remove(item)` -> 移除集合中的指定元素。如果元素不存在,则会发生错误。
- `set.discard(value)` -> 移除指定的集合元素。`remove()` 方法在移除一个不存在的元素时会发生错误,而 `discard()` 方法不会。
- `set.pop()` -> 随机移除一个元素。
- `set.intersection(set1, set2 ...)` -> 返回两个集合的交集。
- `set1 & set2` 返回两个集合的交集。
- `set.intersection_update(set1, set2 ...)` -> 交集,在原始的集合上移除不重叠的元素。
- `set.union(set1, set2...)` -> 返回两个集合的并集。
- `set1 | set2` -> 返回两个集合的并集。
- `set.difference(set)` -> 返回集合的差集。
- `set1 - set2` -> 返回集合的差集。
- `set.difference_update(set)` -> 集合的差集,直接在原来的集合中移除元素,没有返回值。
- `set.symmetric_difference(set)` -> 返回集合的异或。
- `set1 ^ set2` -> 返回集合的异或。
- `set.symmetric_difference_update(set)` -> 移除当前集合中在另外一个指定集合相同的元素,并将另外一个指定集合中不同的元素插入到当前集合中。
- `set.issubset(set)` -> 判断集合是不是被其他集合包含,如果是则返回 True否则返回 False。
- `set1 <= set2` -> 判断集合是不是被其他集合包含,如果是则返回 True否则返回 False。
- `set.issuperset(set)` -> 判断集合是不是包含其他集合,如果是则返回 True否则返回 False。
- `set1 >= set2` -> 判断集合是不是包含其他集合,如果是则返回 True否则返回 False。
- `set.isdisjoint(set)` -> 判断两个集合是不是不相交,如果是返回 True否则返回 False。
**frozenset**
Python 提供了不能改变元素的集合的实现版本,即不能增加或删除元素,类型名叫`frozenset`。需要注意的是`frozenset`仍然可以进行集合操作,只是不能用带有`update`的方法。
- `frozenset([iterable])` -> 返回一个冻结的集合,冻结后集合不能再添加或删除任何元素。
## 2 两个数组的交集
> - 题号349
> - 难度:简单
> - https://leetcode-cn.com/problems/intersection-of-two-arrays/
给定两个数组,编写一个函数来计算它们的交集。
**示例 1:**
```c
: nums1 = [1,2,2,1], nums2 = [2,2]
: [2]
```
**示例 2:**
```c
: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
: [9,4]
```
**说明:**
- 输出结果中的每个元素一定是唯一的。
- 我们可以不考虑输出结果的顺序。
**思路:直接利用集合这种结构**
**C# 语言**
- 执行结果:通过
- 执行用时276 ms, 在所有 C# 提交中击败了 96.33% 的用户
- 内存消耗31.5 MB, 在所有 C# 提交中击败了 100.00% 的用户
```c
public class Solution
{
public int[] Intersection(int[] nums1, int[] nums2)
{
HashSet<int> h1 = new HashSet<int>(nums1);
HashSet<int> h2 = new HashSet<int>(nums2);
return h1.Intersect(h2).ToArray();
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时60 ms, 在所有 Python3 提交中击败了 64.11% 的用户
- 内存消耗13.8 MB, 在所有 Python3 提交中击败了 20.00% 的用户
```python
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
h1 = set(nums1)
h2 = set(nums2)
return list(h1.intersection(h2))
```
## 3 存在重复元素
> - 题号217
> - 难度:简单
> - https://leetcode-cn.com/problems/contains-duplicate/
给定一个整数数组,判断是否存在重复元素。
如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同则返回 false。
<b>示例 1</b>:
```c
: [1,2,3,1]
: true
```
<b>示例 2</b>:
```c
: [1,2,3,4]
: false
```
<b>示例 3</b>:
```c
: [1,1,1,3,3,4,3,2,4,2]
: true
```
**思路:通过集合的方法**
**C# 语言**
- 状态:通过
- 18 / 18 个通过测试用例
- 执行用时: 156 ms, 在所有 C# 提交中击败了 93.33% 的用户
- 内存消耗: 30.3 MB, 在所有 C# 提交中击败了 5.31% 的用户
```c
public class Solution
{
public bool ContainsDuplicate(int[] nums)
{
if (nums.Length < 2)
return false;
HashSet<int> h = new HashSet<int>();
foreach (int num in nums)
{
if (h.Contains(num))
return true;
h.Add(num);
}
return false;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时48 ms, 在所有 Python3 提交中击败了 78.11% 的用户
- 内存消耗18.9 MB, 在所有 Python3 提交中击败了 24.00% 的用户
```python
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
if len(nums) < 2:
return False
h = set()
for num in nums:
if num in h:
return True
h.add(num)
return False
```
## 4 相交链表
> - 题号160
> - 难度:简单
> - https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
![](https://img-blog.csdnimg.cn/20190919184028993.png)
在节点 c1 开始相交。
<b>示例 1</b>
![](https://img-blog.csdnimg.cn/20190919184115602.png)
```c
intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
Reference of the node with value = 8
8 0
A [4,1,8,4,5] B [5,0,1,8,4,5]
A 2 B 3
```
<b>示例 2</b>
![](https://img-blog.csdnimg.cn/20190919184238459.png)
```c
intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
Reference of the node with value = 2
2 0
A [0,9,1,2,4] B [3,2,4]
A 3 B 1
```
<b>示例 3</b>
![](https://img-blog.csdnimg.cn/20190919184347785.png)
```c
intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
null
A [2,6,4] B [1,5]
intersectVal 0 skipA skipB
null
```
<b>注意</b>
- 如果两个链表没有交点,返回 null.
- 在返回结果后,两个链表仍须保持原有的结构。
- 可假定整个链表结构中没有循环。
- 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
**思路:通过集合的方法**
**C# 语言**
- 状态:通过
- 45 / 45 个通过测试用例
- 执行用时: 172 ms, 在所有 C# 提交中击败了 100.00% 的用户
- 内存消耗: 37.6 MB, 在所有 C# 提交中击败了 5.88% 的用户
```c
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int x) { val = x; }
* }
*/
public class Solution
{
public ListNode GetIntersectionNode(ListNode headA, ListNode headB)
{
HashSet<ListNode> hash = new HashSet<ListNode>();
ListNode temp = headA;
while (temp != null)
{
hash.Add(temp);
temp = temp.next;
}
temp = headB;
while (temp != null)
{
if (hash.Contains(temp))
return temp;
temp = temp.next;
}
return null;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时200 ms, 在所有 Python3 提交中击败了 40.19% 的用户
- 内存消耗29.4 MB, 在所有 Python3 提交中击败了 5.00% 的用户
```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> None:
h = set()
temp = headA
while temp is not None:
h.add(temp)
temp = temp.next
temp = headB
while temp is not None:
if temp in h:
return temp
temp = temp.next
return None
```
## 5 环形链表
> - 题号141
> - 难度:简单
> - https://leetcode-cn.com/problems/linked-list-cycle/
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数`pos` 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果`pos`是 -1则在该链表中没有环。
<b>示例 1</b>
```c
head = [3,2,0,-4], pos = 1
true
```
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9hc3NldHMubGVldGNvZGUtY24uY29tL2FsaXl1bi1sYy11cGxvYWQvdXBsb2Fkcy8yMDE4LzEyLzA3L2NpcmN1bGFybGlua2VkbGlzdC5wbmc)
<b>示例 2</b>
```c
head = [1,2], pos = 0
true
```
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9hc3NldHMubGVldGNvZGUtY24uY29tL2FsaXl1bi1sYy11cGxvYWQvdXBsb2Fkcy8yMDE4LzEyLzA3L2NpcmN1bGFybGlua2VkbGlzdF90ZXN0Mi5wbmc)
<b>示例 3</b>
```c
head = [1], pos = -1
false
```
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9hc3NldHMubGVldGNvZGUtY24uY29tL2FsaXl1bi1sYy11cGxvYWQvdXBsb2Fkcy8yMDE4LzEyLzA3L2NpcmN1bGFybGlua2VkbGlzdF90ZXN0My5wbmc)
<b>进阶</b>
你能用 O(1)(即,常量)内存解决此问题吗?
**思路:通过集合的方法**
通过检查一个结点此前是否被访问过来判断链表是否为环形链表。
**C# 语言**
- 状态:通过
- 执行用时112 ms, 在所有 C# 提交中击败了 84.04% 的用户
- 内存消耗26.5 MB, 在所有 C# 提交中击败了 100.00% 的用户
```c
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public bool HasCycle(ListNode head)
{
HashSet<ListNode> h = new HashSet<ListNode>();
ListNode temp = head;
while (temp != null)
{
if (h.Contains(temp))
return true;
h.Add(temp);
temp = temp.next;
}
return false;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时60 ms, 在所有 Python3 提交中击败了 64.49% 的用户
- 内存消耗17.3 MB, 在所有 Python3 提交中击败了 9.52% 的用户
```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
h = set()
temp = head
while temp is not None:
if temp in h:
return True
h.add(temp)
temp = temp.next
return False
```
## 6 环形链表 II
> - 题号142
> - 难度:中等
> - https://leetcode-cn.com/problems/linked-list-cycle-ii/
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1则在该链表中没有环。
<b>说明</b>:不允许修改给定的链表。
<b>示例 1</b>
```c
head = [3,2,0,-4], pos = 1
tail connects to node index 1
```
![](https://img-blog.csdnimg.cn/20190917082846690.png)
<b>示例 2</b>
```c
head = [1,2], pos = 0
tail connects to node index 0
```
![](https://img-blog.csdnimg.cn/20190917082911889.png)
<b>示例 3</b>
```c
head = [1], pos = -1
no cycle
```
![](https://img-blog.csdnimg.cn/20190917082930392.png)
<b>进阶:</b>
你是否可以不用额外空间解决此题?
<b>思路:通过集合的方法</b>
**C# 语言**
- 状态:通过
- 16 / 16 个通过测试用例
- 执行用时: 140 ms, 在所有 C# 提交中击败了 82.93% 的用户
- 内存消耗: 26 MB, 在所有 C# 提交中击败了 5.00% 的用户
```c
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution
{
public ListNode DetectCycle(ListNode head)
{
HashSet<ListNode> h = new HashSet<ListNode>();
ListNode temp = head;
while (temp != null)
{
if (h.Contains(temp))
return temp;
h.Add(temp);
temp = temp.next;
}
return null;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时72 ms, 在所有 Python3 提交中击败了 36.52% 的用户
- 内存消耗17.2 MB, 在所有 Python3 提交中击败了 7.69% 的用户
```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
h = set()
temp = head
while temp is not None:
if temp in h:
return temp
h.add(temp)
temp = temp.next
return None
```
## 7 快乐数
> - 题号202
> - 难度:简单
> - https://leetcode-cn.com/problems/happy-number/
编写一个算法来判断一个数是不是“快乐数”。
一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1也可能是无限循环但始终变不到 1。如果可以变为 1那么这个数就是快乐数。
示例:
```c
: 19
: true
:
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1
7
true
: 20
: false
20 => 4 + 0
4 => 16
16 => 1 + 36
37 => 9 + 49
58 => 25 + 64
89 => 64 + 81
145 => 1 + 16 + 25
42 => 16 + 4
20 , 20, 1
```
**思路:通过集合的方法**
**C# 语言**
- 执行结果:通过
- 执行用时48 ms, 在所有 C# 提交中击败了 80.74% 的用户
- 内存消耗17 MB, 在所有 C# 提交中击败了 100.00% 的用户
```c
public class Solution
{
public bool IsHappy(int n)
{
HashSet<int> h = new HashSet<int>();
int m = 0;
while (true)
{
while (n != 0)
{
m += (int)Math.Pow(n % 10,2);
n /= 10;
}
if (m == 1)
{
return true;
}
if (h.Contains(m))
{
return false;
}
h.Add(m);
n = m;
m = 0;
}
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时40 ms, 在所有 Python3 提交中击败了 79.79% 的用户
- 内存消耗13.8 MB, 在所有 Python3 提交中击败了 9.09% 的用户
```python
class Solution:
def isHappy(self, n: int) -> bool:
h = set()
m = 0
while True:
while n != 0:
m += (n % 10) ** 2
n //= 10
if m == 1:
return True
if m in h:
return False
h.add(m)
n = m
m = 0
```
## 8 只出现一次的数字
> - 题号136
> - 难度:简单
> - https://leetcode-cn.com/problems/single-number/
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
<b>说明</b>
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
<b>示例 1</b>:
```c
: [2,2,1]
: 1
```
<b>示例 2</b>:
```c
: [4,1,2,1,2]
: 4
```
**思路:通过集合的方法**
**C# 语言**
- 状态:通过
- 16 / 16 个通过测试用例
- 执行用时: 136 ms, 在所有 C# 提交中击败了 98.86% 的用户
- 内存消耗: 26.4 MB, 在所有 C# 提交中击败了 5.34% 的用户
```c
public class Solution
{
public int SingleNumber(int[] nums)
{
HashSet<int> h = new HashSet<int>();
for (int i = 0; i < nums.Length; i++)
{
if (h.Contains(nums[i]))
{
h.Remove(nums[i]);
}
else
{
h.Add(nums[i]);
}
}
return h.ElementAt(0);
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时60 ms, 在所有 Python3 提交中击败了 55.88% 的用户
- 内存消耗15.6 MB, 在所有 Python3 提交中击败了 5.26% 的用户
```python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
h = set()
for num in nums:
if num in h:
h.remove(num)
else:
h.add(num)
return list(h)[0]
```
## 9 不邻接植花
> - 题号1042
> - 难度:简单
> - https://leetcode-cn.com/problems/flower-planting-with-no-adjacent/
`N` 个花园,按从 `1``N` 标记。在每个花园中,你打算种下四种花之一。
`paths[i] = [x, y]` 描述了花园 `x` 到花园 `y` 的双向路径。
另外,没有花园有 3 条以上的路径可以进入或者离开。
你需要为每个花园选择一种花,使得通过边相连的任何两个花园中的花的种类互不相同。
以数组形式返回选择的方案作为答案 `answer`,其中 `answer[i]` 为在第 `(i+1)` 个花园中种植的花的种类。花的种类用 1, 2, 3, 4 表示。保证存在答案。
**示例 1**
```c
N = 3, paths = [[1,2],[2,3],[3,1]]
[1,2,3]
```
**示例 2**
```c
N = 4, paths = [[1,2],[3,4]]
[1,2,1,2]
```
**示例 3**
```c
N = 4, paths = [[1,2],[2,3],[3,4],[4,1],[1,3],[2,4]]
[1,2,3,4]
```
**思路**:利用 字典 + 集合 构造图的邻接表。
**C# 语言**
- 执行结果:通过
- 执行用时440 ms, 在所有 C# 提交中击败了 100.00% 的用户
- 内存消耗48.9 MB, 在所有 C# 提交中击败了 100.00% 的用户
```c
public class Solution
{
public int[] GardenNoAdj(int N, int[][] paths)
{
Dictionary<int, HashSet<int>> graph = new Dictionary<int, HashSet<int>>();
for (int i = 0; i < N; i++)
{
graph.Add(i, new HashSet<int>());
}
foreach (int[] path in paths)
{
int i = path[0] - 1;
int j = path[1] - 1;
graph[i].Add(j);
graph[j].Add(i);
}
int[] result = new int[N];
for (int i = 0; i < N; i++)
{
bool[] visited = new bool[5];
foreach (int adj in graph[i])
{
visited[result[adj]] = true;
}
for (int j = 1; j <= 4; j++)
{
if (visited[j] == false)
{
result[i] = j;
break;
}
}
}
return result;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时536 ms, 在所有 Python3 提交中击败了 62.29% 的用户
- 内存消耗20.6 MB, 在所有 Python3 提交中击败了 33.33% 的用户
```python
class Solution:
def gardenNoAdj(self, N: int, paths: List[List[int]]) -> List[int]:
graph = {i: set() for i in range(0, N)}
for path in paths:
i = path[0] - 1
j = path[1] - 1
graph[i].add(j)
graph[j].add(i)
result = [0] * N
for i in range(N):
visited = [False] * 5
for adj in graph[i]:
visited[result[adj]] = True
for j in range(1, 5):
if visited[j] is False:
result[i] = j
break
return result
```

View File

@@ -0,0 +1,735 @@
# 字典技术在求解算法题中的应用
## 1 C# 和 Python 中的字典结构
**C# 中字典的常用方法**
对于 C# 中的 `Dictionary`类 相信大家都不陌生,这是一个 `Collection`(集合) 类型,可以通过 Key/Value (键值对) 的形式来存放数据;该类最大的优点就是它查找元素的时间复杂度接近 O(1),实际项目中常被用来做一些数据的本地缓存,提升整体效率。
常用方法如下:
- `public Dictionary();` -> 构造函数
- `public Dictionary(int capacity);` -> 构造函数
- `public void Add(TKey key, TValue value);` -> 将指定的键和值添加到字典中。
- `public bool Remove(TKey key);` -> 将带有指定键的值移除。
- `public void Clear();` -> 将所有键和值从字典中移除。
- `public bool ContainsKey(TKey key);` -> 确定是否包含指定键。
- `public bool ContainsValue(TValue value);` -> 确定否包含特定值。
- `public TValue this[TKey key] { get; set; }` -> 获取或设置与指定的键关联的值。
- `public KeyCollection Keys { get; }` -> 获得键的集合。
- `public ValueCollection Values { get; }` -> 获得值的集合。
举例如下:
```c
public static void DicSample()
{
Dictionary<string, string> dic = new Dictionary<string, string>();
try
{
if (dic.ContainsKey("Item1") == false)
{
dic.Add("Item1", "ZheJiang");
}
if (dic.ContainsKey("Item2") == false)
{
dic.Add("Item2", "ShangHai");
}
else
{
dic["Item2"] = "ShangHai";
}
if (dic.ContainsKey("Item3") == false)
{
dic.Add("Item3", "BeiJing");
}
}
catch (Exception e)
{
Console.WriteLine("Error: {0}", e.Message);
}
if (dic.ContainsKey("Item1"))
{
Console.WriteLine("Output: " + dic["Item1"]);
}
foreach (string key in dic.Keys)
{
Console.WriteLine("Output Key: {0}", key);
}
foreach (string value in dic.Values)
{
Console.WriteLine("Output Value: {0}", value);
}
foreach (KeyValuePair<string, string> item in dic)
{
Console.WriteLine("Output Key : {0}, Value : {1} ", item.Key, item.Value);
}
}
// Output: ZheJiang
// Output Key: Item1
// Output Key: Item2
// Output Key: Item3
// Output Value: ZheJiang
// Output Value: ShangHai
// Output Value: BeiJing
// Output Key: Item1, Value: ZheJiang
// Output Key: Item2, Value: ShangHai
// Output Key: Item3, Value: BeiJing
```
注意:增加键值对之前需要判断是否存在该键,如果已经存在该键而不判断,将抛出异常。
有关更多 字典 的知识参见图文:
- [浅析 C# Dictionary实现原理](https://mp.weixin.qq.com/s/Qmzk6n_AYi7chhDD2m8aHw)
**Python 中字典的常用方法**
Python中的 字典 是无序的 键:值(`key:value`)对集合,在同一个字典之内键必须是互不相同的。
- `dict` 内部存放的顺序和 `key` 放入的顺序是没有关系的。
- `dict` 查找和插入的速度极快,不会随着 `key` 的增加而增加,但是需要占用大量的内存。
字典 定义语法为 `{元素1, 元素2, ..., 元素n}`
- 其中每一个元素是一个「键值对」-- 键:值 (`key:value`)
- 关键点是「大括号 {}」,「逗号 ,」和「冒号 :」
- 大括号 -- 把所有元素绑在一起
- 逗号 -- 将每个键值对分开
- 冒号 -- 将键和值分开
常用方法如下:
- `dict()` -> 构造函数。
- `dict(mapping)` -> 构造函数。
- `dict(**kwargs)` -> 构造函数。
- `dict.keys()` -> 返回一个可迭代对象,可以使用 `list()` 来转换为列表,列表为字典中的所有键。
- `dict.values()` -> 返回一个迭代器,可以使用 `list()` 来转换为列表,列表为字典中的所有值。
- `dict.items()` -> 以列表返回可遍历的 (键, 值) 元组数组。
- `dict.get(key, default=None)` -> 返回指定键的值,如果值不在字典中返回默认值。
- `dict.setdefault(key, default=None)` -> 和`get()`方法 类似, 如果键不存在于字典中,将会添加键并将值设为默认值。
- `key in dict` -> `in` 操作符用于判断键是否存在于字典中,如果键在字典 dict 里返回`true`,否则返回`false`
- `key not in dict` -> `not in`操作符刚好相反,如果键在字典 dict 里返回`false`,否则返回`true`
- `dict.pop(key[,default])` -> 删除字典给定键 `key` 所对应的值,返回值为被删除的值。`key` 值必须给出。若`key`不存在,则返回 `default` 值。
- `del dict[key]` -> 删除字典给定键 `key` 所对应的值。
举例如下:
```python
def DicSample(self):
dic = dict()
try:
if "Item1" not in dic:
dic["Item1"] = "ZheJiang"
if "Item2" not in dic:
dic.setdefault("Item2", "ShangHai")
else:
dic["Item2"] = "ShangHai"
dic["Item3"] = "BeiJing"
except KeyError as error:
print("Error:{0}".format(str(error)))
if "Item1" in dic:
print("Output: {0}".format(dic["Item1"]))
for key in dic.keys():
print("Output Key: {0}".format(key))
for value in dic.values():
print("Output Value: {0}".format(value))
for key, value in dic.items():
print("Output Key: {0}, Value: {1}".format(key, value))
# Output: ZheJiang
# Output Key: Item1
# Output Key: Item2
# Output Key: Item3
# Output Value: ZheJiang
# Output Value: ShangHai
# Output Value: BeiJing
# Output Key: Item1, Value: ZheJiang
# Output Key: Item2, Value: ShangHai
# Output Key: Item3, Value: BeiJing
```
## 2 两数之和
> - 题号1
> - 难度:简单
> - https://leetcode-cn.com/problems/two-sum/
给定一个整数数组 `nums` 和一个目标值 `target`,请你在该数组中找出和为目标值的那 <b>两个整数</b>,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
**示例1:**
```c
nums = [2, 7, 11, 15], target = 9
nums[0] + nums[1] = 2 + 7 = 9 [0, 1]
```
**示例2**
```c
nums = [230, 863, 916, 585, 981, 404, 316, 785, 88, 12, 70, 435, 384, 778, 887, 755, 740, 337, 86, 92, 325, 422, 815, 650, 920, 125, 277, 336, 221, 847, 168, 23, 677, 61, 400, 136, 874, 363, 394, 199, 863, 997, 794, 587, 124, 321, 212, 957, 764, 173, 314, 422, 927, 783, 930, 282, 306, 506, 44, 926, 691, 568, 68, 730, 933, 737, 531, 180, 414, 751, 28, 546, 60, 371, 493, 370, 527, 387, 43, 541, 13, 457, 328, 227, 652, 365, 430, 803, 59, 858, 538, 427, 583, 368, 375, 173, 809, 896, 370, 789], target = 542
nums[28] + nums[45] = 221 + 321 = 542 [28, 45]
```
**思路:利用字典的方式**
把字典当作一个存储容器,`key` 存储已经出现的数字,`value` 存储数组的下标。
**C# 语言**
- 执行结果:通过
- 执行用时280 ms, 在所有 C# 提交中击败了 96.53% 的用户
- 内存消耗31.1 MB, 在所有 C# 提交中击败了 6.89% 的用户
```c
public class Solution
{
public int[] TwoSum(int[] nums, int target)
{
int[] result = new int[2];
Dictionary<int, int> dic = new Dictionary<int, int>();
for (int i = 0; i < nums.Length; i++)
{
int find = target - nums[i];
if (dic.ContainsKey(find))
{
result[0] = dic[find];
result[1] = i;
break;
}
if (dic.ContainsKey(nums[i]) == false)
dic.Add(nums[i], i);
}
return result;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时52 ms, 在所有 Python3 提交中击败了 86.77% 的用户
- 内存消耗15.1 MB, 在所有 Python3 提交中击败了 7.35% 的用户
```python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
result = list()
dic = dict()
for index, val in enumerate(nums):
find = target - val
if find in dic is not None:
result = [dic[find], index]
break
else:
dic[val] = index
return result
```
## 3 只出现一次的数字 II
> - 题号137
> - 难度:中等
> - https://leetcode-cn.com/problems/single-number-ii/
给定一个 **非空** 整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
**说明:**
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
**示例 1:**
```c
: [2,2,3,2]
: 3
```
**示例 2:**
```c
: [0,1,0,1,0,1,99]
: 99
```
**思路:利用字典的方式**
把字典当作一个存储容器,`key` 存储数组中的数字,`value` 存储该数字出现的频数。
**C# 语言**
- 执行结果:通过
- 执行用时112 ms, 在所有 C# 提交中击败了 91.53% 的用户
- 内存消耗25.4 MB, 在所有 C# 提交中击败了 100.00% 的用户
```c
public class Solution
{
public int SingleNumber(int[] nums)
{
Dictionary<int, int> dict = new Dictionary<int, int>();
for (int i = 0; i < nums.Length; i++)
{
if (dict.ContainsKey(nums[i]))
{
dict[nums[i]]++;
}
else
{
dict.Add(nums[i], 1);
}
}
return dict.Single(a => a.Value == 1).Key;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时40 ms, 在所有 Python3 提交中击败了 89.20% 的用户
- 内存消耗15.1 MB, 在所有 Python3 提交中击败了 25.00% 的用户
```python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
dic = dict()
for num in nums:
if num in dic:
dic[num] += 1
else:
dic[num] = 1
for k, v in dic.items():
if v == 1:
return k
return -1
```
## 4 罗马数字转整数
> - 题号13
> - 难度:简单
> - https://leetcode-cn.com/problems/roman-to-integer/
罗马数字包含以下七种字符: `I V X LCD``M`
```
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
```
例如, 罗马数字 2 写做`II`,即为两个并列的 1。12 写做`XII`,即为`X + II`。 27 写做`XXVII`, 即为`XX + V + II`
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做`IIII`,而是`IV`。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为`IX`。这个特殊的规则只适用于以下六种情况:
```
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
```
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。
**示例 1:**
```c
:"III"
: 3
```
**示例 2:**
```c
: "IV"
: 4
```
**示例 3:**
```c
: "IX"
: 9
```
**示例 4:**
```c
: "LVIII"
: 58
: L = 50, V= 5, III = 3.
```
**示例 5:**
```c
: "MCMXCIV"
: 1994
: M = 1000, CM = 900, XC = 90, IV = 4.
```
**思路:利用字典的方式**
把字典当作一个存储容器,`key` 存储罗马字符的所有组合,`value` 存储该组合代表的值。
每次取一个字符,判断这个字符之后是否还有字符。如果有,则判断这两个字符是否在字典中,如果存在则取值。否则,按照一个字符去取值即可。
**C# 语言**
- 执行结果:通过
- 执行用时120 ms, 在所有 C# 提交中击败了 42.16% 的用户
- 内存消耗25.8 MB, 在所有 C# 提交中击败了 5.27% 的用户
```c
public class Solution
{
public int RomanToInt(string s)
{
Dictionary<string, int> dic = new Dictionary<string, int>();
dic.Add("I", 1);
dic.Add("II", 2);
dic.Add("IV", 4);
dic.Add("IX", 9);
dic.Add("X", 10);
dic.Add("XL", 40);
dic.Add("XC", 90);
dic.Add("C", 100);
dic.Add("CD", 400);
dic.Add("CM", 900);
dic.Add("V", 5);
dic.Add("L", 50);
dic.Add("D", 500);
dic.Add("M", 1000);
int result = 0;
int count = s.Length;
int i = 0;
while (i < count)
{
char c = s[i];
if (i + 1 < count && dic.ContainsKey(s.Substring(i, 2)))
{
result += dic[s.Substring(i, 2)];
i += 2;
}
else
{
result += dic[c.ToString()];
i += 1;
}
}
return result;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时72 ms, 在所有 Python3 提交中击败了 24.93% 的用户
- 内存消耗13.5 MB, 在所有 Python3 提交中击败了 5.05% 的用户
```python
class Solution:
def romanToInt(self, s: str) -> int:
dic = {"I": 1, "II": 2, "IV": 4, "IX": 9, "X": 10, "XL": 40, "XC": 90,
"C": 100, "CD": 400, "CM": 900, "V": 5,
"L": 50, "D": 500, "M": 1000}
result = 0
count = len(s)
i = 0
while i < count:
c = s[i]
if i + 1 < count and s[i:i + 2] in dic:
result += dic[s[i:i + 2]]
i += 2
else:
result += dic[c]
i += 1
return result
```
## 5 LRU缓存机制
> - 题号146
> - 难度:中等
> - https://leetcode-cn.com/problems/lru-cache/
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 `get(key)` - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 `put(key, value)` - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
<b>进阶</b>:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
<b>示例</b>:
```c
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
```
<b>思路:利用 字典 + 列表 的方式</b>
> 计算机的缓存容量有限,如果缓存满了就要删除一些内容,给新内容腾位置。但问题是,删除哪些内容呢?我们肯定希望删掉哪些没什么用的缓存,而把有用的数据继续留在缓存里,方便之后继续使用。那么,什么样的数据,我们判定为「有用的」的数据呢?
>
> LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used也就是说我们认为最近使用过的数据应该是是「有用的」很久都没用过的数据应该是无用的内存满了就优先删那些很久没用过的数据。
把字典当作一个存储容器,由于字典是无序的,即 `dict` 内部存放的顺序和 `key` 放入的顺序是没有关系的,所以需要一个 `list` 来辅助排序。
**C# 语言**
- 状态:通过
- 18 / 18 个通过测试用例
- 执行用时: 392 ms, 在所有 C# 提交中击败了 76.56% 的用户
- 内存消耗: 47.9 MB, 在所有 C# 提交中击败了 20.00% 的用户
```c
public class LRUCache
{
private readonly List<int> _keys;
private readonly Dictionary<int, int> _dict;
public LRUCache(int capacity)
{
_keys = new List<int>(capacity);
_dict = new Dictionary<int, int>(capacity);
}
public int Get(int key)
{
if (_dict.ContainsKey(key))
{
_keys.Remove(key);
_keys.Add(key);
return _dict[key];
}
return -1;
}
public void Put(int key, int value)
{
if (_dict.ContainsKey(key))
{
_dict.Remove(key);
_keys.Remove(key);
}
else if (_keys.Count == _keys.Capacity)
{
_dict.Remove(_keys[0]);
_keys.RemoveAt(0);
}
_keys.Add(key);
_dict.Add(key, value);
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.Get(key);
* obj.Put(key,value);
*/
```
**Python 语言**
- 执行结果:通过
- 执行用时628 ms, 在所有 Python3 提交中击败了 12.15% 的用户
- 内存消耗22 MB, 在所有 Python3 提交中击败了 65.38% 的用户
```python
class LRUCache:
def __init__(self, capacity: int):
self._capacity = capacity
self._dict = dict()
self._keys = list()
def get(self, key: int) -> int:
if key in self._dict:
self._keys.remove(key)
self._keys.append(key)
return self._dict[key]
return -1
def put(self, key: int, value: int) -> None:
if key in self._dict:
self._dict.pop(key)
self._keys.remove(key)
elif len(self._keys) == self._capacity:
self._dict.pop(self._keys[0])
self._keys.remove(self._keys[0])
self._keys.append(key)
self._dict[key] = value
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
```
注意,这两行代码不能颠倒顺序,否则`dict`中就不会存在`_keys[0]`了。
```python
self._dict.pop(self._keys[0])
self._keys.remove(self._keys[0])
```
## 6 不邻接植花
> - 题号1042
> - 难度:简单
> - https://leetcode-cn.com/problems/flower-planting-with-no-adjacent/
`N` 个花园,按从 `1``N` 标记。在每个花园中,你打算种下四种花之一。
`paths[i] = [x, y]` 描述了花园 `x` 到花园 `y` 的双向路径。
另外,没有花园有 3 条以上的路径可以进入或者离开。
你需要为每个花园选择一种花,使得通过边相连的任何两个花园中的花的种类互不相同。
以数组形式返回选择的方案作为答案 `answer`,其中 `answer[i]` 为在第 `(i+1)` 个花园中种植的花的种类。花的种类用 1, 2, 3, 4 表示。保证存在答案。
**示例 1**
```c
N = 3, paths = [[1,2],[2,3],[3,1]]
[1,2,3]
```
**示例 2**
```c
N = 4, paths = [[1,2],[3,4]]
[1,2,1,2]
```
**示例 3**
```c
N = 4, paths = [[1,2],[2,3],[3,4],[4,1],[1,3],[2,4]]
[1,2,3,4]
```
**思路**:利用 字典 + 集合 构造图的邻接表。
**C# 语言**
- 执行结果:通过
- 执行用时440 ms, 在所有 C# 提交中击败了 100.00% 的用户
- 内存消耗48.9 MB, 在所有 C# 提交中击败了 100.00% 的用户
```c
public class Solution
{
public int[] GardenNoAdj(int N, int[][] paths)
{
Dictionary<int, HashSet<int>> graph = new Dictionary<int, HashSet<int>>();
for (int i = 0; i < N; i++)
{
graph.Add(i, new HashSet<int>());
}
foreach (int[] path in paths)
{
int i = path[0] - 1;
int j = path[1] - 1;
graph[i].Add(j);
graph[j].Add(i);
}
int[] result = new int[N];
for (int i = 0; i < N; i++)
{
bool[] visited = new bool[5];
foreach (int adj in graph[i])
{
visited[result[adj]] = true;
}
for (int j = 1; j <= 4; j++)
{
if (visited[j] == false)
{
result[i] = j;
break;
}
}
}
return result;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时536 ms, 在所有 Python3 提交中击败了 62.29% 的用户
- 内存消耗20.6 MB, 在所有 Python3 提交中击败了 33.33% 的用户
```python
class Solution:
def gardenNoAdj(self, N: int, paths: List[List[int]]) -> List[int]:
graph = {i: set() for i in range(0, N)}
for path in paths:
i = path[0] - 1
j = path[1] - 1
graph[i].add(j)
graph[j].add(i)
result = [0] * N
for i in range(N):
visited = [False] * 5
for adj in graph[i]:
visited[result[adj]] = True
for j in range(1, 5):
if visited[j] is False:
result[i] = j
break
return result
```

View File

@@ -0,0 +1,519 @@
# 排序技术在求解算法题中的应用
## 1 C# 和 Python 中的排序操作
**C# 中的排序**
对集合类的排序,我们通常使用位于 `System.Core` 程序集,`System.Linq`命名空间下,`Enumerable`静态类中的扩展方法。
```c
public static class Enumerable
{
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
}
```
`OrderBy`是对`IEnumerable<T>`类型的扩展,而`IEnumerable<T>`是整个 LINQ 的基础。
C# 大部分数据结构都实现了`IEnumerable<T>`,比如:`List<T>``IDictionary<K,T>``LinkedList<T>``Stack<T>``Queue<T>`等,都可以使用 LINQ 中的扩展方法。
有关更多扩展方法的知识参见图文:
- [浅析 C# 语言中的扩展方法](https://mp.weixin.qq.com/s/F3YaE4mmfCKDkkkesY0HrA)
**Python 中的排序**
对列表的排序通常有两种方法,第一种使用`list`本身的`sort`方法,第二种使用 Python 的内置方法`sorted`
`list.sort( key=None, reverse=False)` 对原列表进行排序。
- `key` -- 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
- `reverse` -- 排序规则,`reverse = True` 降序, `reverse = False` 升序(默认)。
- 该方法没有返回值,但是会对列表的对象进行排序。
Sample01
```python
list1 = [123, 456, 789, 213]
list1.sort()
print(list1) # [123, 213, 456, 789]
list1.sort(reverse=True)
print(list1) # [789, 456, 213, 123]
```
Sample02
```python
# 获取列表的第二个元素
def takeSecond(elem):
return elem[1]
r = [(2, 2), (3, 4), (4, 1), (1, 3)]
r.sort(key=takeSecond)
print(r)
# [(4, 1), (2, 2), (1, 3), (3, 4)]
```
`sorted(iterable, key=None, reverse=False) ` 对所有可迭代的对象进行排序操作。
- `iterable` -- 可迭代对象。
- `key` -- 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
- `reverse` -- 排序规则,`reverse = True` 降序 `reverse = False` 升序(默认)。
- 返回重新排序的列表。
Sample01
```python
numbers = [-8, 99, 3, 7, 83]
print(sorted(numbers)) # [-8, 3, 7, 83, 99]
print(sorted(numbers, reverse=True)) # [99, 83, 7, 3, -8]
```
Sample02
```python
array = [{"age": 20, "name": "a"}, {"age": 25, "name": "b"}, {"age": 10, "name": "c"}]
array = sorted(array, key=lambda x: x["age"])
print(array)
# [{'age': 10, 'name': 'c'}, {'age': 20, 'name': 'a'}, {'age': 25, 'name': 'b'}]
```
## 2 求众数
> - 题号169
> - 难度:简单
> - https://leetcode-cn.com/problems/majority-element/
给定一个大小为 `n` 的数组,找到其中的众数。众数是指在数组中出现次数大于` ⌊ n/2 ⌋` 的元素。
你可以假设数组是非空的,并且给定的数组总是存在众数。
<b>示例 1:</b>
```c
: [3,2,3]
: 3
```
<b>示例 2:</b>
```c
: [2,2,1,1,1,2,2]
: 2
```
**思路:利用排序的方法**
**C# 语言**
- 状态:通过
- 44 / 44 个通过测试用例
- 执行用时192 ms
```c
public class Solution
{
public int MajorityElement(int[] nums)
{
nums = nums.OrderBy(a => a).ToArray();
return nums[nums.Length / 2];
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时48 ms, 在所有 Python3 提交中击败了 82.08% 的用户
- 内存消耗15.2 MB, 在所有 Python3 提交中击败了 6.90% 的用户
```python
class Solution:
def majorityElement(self, nums: List[int]) -> int:
nums.sort()
return nums[len(nums) // 2]
```
## 3 数组中的第K个最大元素
> - 题号215
> - 难度:中等
> - https://leetcode-cn.com/problems/kth-largest-element-in-an-array/
在未排序的数组中找到第 `k` 个最大的元素。请注意,你需要找的是数组排序后的第 `k` 个最大的元素,而不是第 `k` 个不同的元素。
<b>示例 1</b>:
```c
: [3,2,1,5,6,4] k = 2
: 5
```
<b>示例 2</b>:
```c
: [3,2,3,1,2,4,5,5,6] k = 4
: 4
```
<b>说明</b>:
你可以假设 `k` 总是有效的,且 `1 ≤ k ≤ 数组的长度`
**思路:利用排序的方法**
**C# 语言**
- 状态:通过
- 32 / 32 个通过测试用例
- 执行用时: 152 ms, 在所有 C# 提交中击败了 76.47% 的用户
- 内存消耗: 24.6 MB, 在所有 C# 提交中击败了 5.55% 的用户
```c
public class Solution
{
public int FindKthLargest(int[] nums, int k)
{
nums = nums.OrderBy(a => a).ToArray();
return nums[nums.Length - k];
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时40 ms, 在所有 Python3 提交中击败了 92.64% 的用户
- 内存消耗14.4 MB, 在所有 Python3 提交中击败了 15.79% 的用户
```python
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
nums.sort()
return nums[len(nums) - k]
```
## 4 两个数组的交集 II
> - 题号350
> - 难度:简单
> - https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/
给定两个数组,编写一个函数来计算它们的交集。
**示例 1:**
```c
: nums1 = [1,2,2,1], nums2 = [2,2]
: [2,2]
```
**示例 2:**
```c
: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
: [4,9]
```
**说明:**
- 输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
- 我们可以不考虑输出结果的顺序。
**进阶:**
- 如果给定的数组已经排好序呢?你将如何优化你的算法?
- 如果`nums1`的大小比`nums2`小很多,哪种方法更优?
- 如果`nums2`的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
**思路:利用 排序 + 双索引 的方法**
**C# 语言**
- 执行结果:通过
- 执行用时320 ms, 在所有 C# 提交中击败了 23.03% 的用户
- 内存消耗31.2 MB, 在所有 C# 提交中击败了 100.00% 的用户
```c
public class Solution
{
public int[] Intersect(int[] nums1, int[] nums2)
{
nums1 = nums1.OrderBy(a => a).ToArray();
nums2 = nums2.OrderBy(a => a).ToArray();
List<int> result = new List<int>();
int i = 0, j = 0;
while (i < nums1.Length && j < nums2.Length)
{
if (nums1[i] < nums2[j])
{
i++;
}
else if (nums1[i] > nums2[j])
{
j++;
}
else
{
result.Add(nums1[i]);
i++;
j++;
}
}
return result.ToArray();
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时64 ms, 在所有 Python3 提交中击败了 62.00% 的用户
- 内存消耗13.7 MB, 在所有 Python3 提交中击败了 12.50% 的用户
```python
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1.sort()
nums2.sort()
result = []
i, j = 0, 0
while i < len(nums1) and j < len(nums2):
if nums1[i] < nums2[j]:
i += 1
elif nums1[i] > nums2[j]:
j += 1
else:
result.append(nums1[i])
i += 1
j += 1
return result
```
## 5 最接近的三数之和
> - 题号16
> - 难度:中等
> - https://leetcode-cn.com/problems/3sum-closest/
给定一个包括`n`个整数的数组`nums`和一个目标值`target`。找出`nums`中的三个整数,使得它们的和与`target`最接近。返回这三个数的和。假定每组输入只存在唯一答案。
<b>示例</b> :
```c
nums = [-121-4], target = 1.
target 2. (-1 + 2 + 1 = 2).
```
**思路:利用 排序 + 三索引 的方法**
<b>C# 实现</b>
- 状态:通过
- 125 / 125 个通过测试用例
- 执行用时: 132 ms, 在所有 C# 提交中击败了 100.00% 的用户
- 内存消耗: 24 MB, 在所有 C# 提交中击败了 5.55% 的用户
```c
public class Solution
{
public int ThreeSumClosest(int[] nums, int target)
{
nums = nums.OrderBy(a => a).ToArray();
int result = nums[0] + nums[1] + nums[2];
for (int i = 0; i < nums.Length - 2; i++)
{
int start = i + 1, end = nums.Length - 1;
while (start < end)
{
int sum = nums[start] + nums[end] + nums[i];
if (Math.Abs(target - sum) < Math.Abs(target - result))
result = sum;
if (sum > target)
end--;
else if (sum < target)
start++;
else
return result;
}
}
return result;
}
}
```
<b>Pyhton 实现</b>
- 执行结果:通过
- 执行用时124 ms, 在所有 Python3 提交中击败了 72.19% 的用户
- 内存消耗13.2 MB, 在所有 Python3 提交中击败了 22.06% 的用户
```python
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums = sorted(nums)
result = nums[0] + nums[1] + nums[2]
for i in range(0, len(nums) - 2):
start = i + 1
end = len(nums) - 1
while start < end:
sum = nums[start] + nums[end] + nums[i]
if abs(target - sum) < abs(target - result):
result = sum
if sum > target:
end -= 1
elif sum < target:
start += 1
else:
return result
return result
```
## 6 三数之和
> - 题号15
> - 难度:中等
> - https://leetcode-cn.com/problems/3sum/
给定一个包含`n`个整数的数组`nums`,判断`nums`中是否存在三个元素`abc`,使得`a + b + c = 0`?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
```c
nums = [-1, 0, 1, 2, -1, -4]
[
[-1, 0, 1],
[-1, -1, 2]
]
```
**思路:利用 排序 + 三索引 的方法**
为了避免三次循环,提升执行效率。首先,对`nums`进行排序。然后固定3个索引`i,l(left),r(right)``i`进行最外层循环,`l`指向`nums[i]`之后数组的最小值,`r`指向`nums[i]`之后数组的最大值。模仿快速排序的思路,如果`nums[i] > 0`就不需要继续计算了,否则计算`nums[i] + nums[l] + nums[r]`是否等于零并进行相应的处理。如果大于零,向`l`方向移动`r`指针,如果小于零,向`r`方向移动`l`索引如果等于零则加入到存储最后结果的result链表中。当然题目中要求这个三元组不可重复所以在进行的过程中加入去重就好。
**C# 实现**
- 执行结果:通过
- 执行用时348 ms, 在所有 C# 提交中击败了 99.54% 的用户
- 内存消耗35.8 MB, 在所有 C# 提交中击败了 6.63% 的用户
```c
public class Solution
{
public IList<IList<int>> ThreeSum(int[] nums)
{
IList<IList<int>> result = new List<IList<int>>();
nums = nums.OrderBy(a => a).ToArray();
int len = nums.Length;
for (int i = 0; i < len - 2; i++)
{
if (nums[i] > 0)
break; // 如果最小的数字大于0, 后面的操作已经没有意义
if (i > 0 && nums[i - 1] == nums[i])
continue; // 跳过三元组中第一个元素的重复数据
int l = i + 1;
int r = len - 1;
while (l < r)
{
int sum = nums[i] + nums[l] + nums[r];
if (sum < 0)
{
l++;
}
else if (sum > 0)
{
r--;
}
else
{
result.Add(new List<int>() {nums[i], nums[l], nums[r]});
// 跳过三元组中第二个元素的重复数据
while (l < r && nums[l] == nums[l + 1])
{
l++;
}
// 跳过三元组中第三个元素的重复数据
while (l < r && nums[r - 1] == nums[r])
{
r--;
}
l++;
r--;
}
}
}
return result;
}
}
```
**Python 实现**
- 执行结果:通过
- 执行用时660 ms, 在所有 Python3 提交中击败了 95.64% 的用户
- 内存消耗16.1 MB, 在所有 Python3 提交中击败了 75.29% 的用户
```python
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums = sorted(nums)
result = []
for i in range(0, len(nums) - 2):
# 如果最小的数字大于0, 后面的操作已经没有意义
if nums[i] > 0:
break
# 跳过三元组中第一个元素的重复数据
if i > 0 and nums[i-1] == nums[i]:
continue
# 限制nums[i]是三元组中最小的元素
l = i + 1
r = len(nums) - 1
while l < r:
sum = nums[i] + nums[l] + nums[r]
if sum < 0:
l += 1
elif sum > 0:
r -= 1
else:
result.append([nums[i], nums[l], nums[r]])
# 跳过三元组中第二个元素的重复数据
while l < r and nums[l] == nums[l+1]:
l += 1
# 跳过三元组中第三个元素的重复数据
while l < r and nums[r] == nums[r-1]:
r -= 1
l += 1
r -= 1
return result
```

View File

@@ -0,0 +1,902 @@
# 位运算技术在求解算法题中的应用
## 1 C# 和 Python 中的位运算操作
**1. 原码、反码和补码**
二进制有三种不同的表示形式:原码、反码和补码,++计算机内部使用补码来表示++。
原码:就是其二进制表示(注意,有一位符号位)。
```c
00 00 00 11 -> 3
10 00 00 11 -> -3
```
反码:正数的反码就是原码,负数的反码是符号位不变,其余位取反(对应正数按位取反)。
```c
00 00 00 11 -> 3
11 11 11 00 -> -3
```
补码:正数的补码就是原码,负数的补码是反码+1。
```c
00 00 00 11 -> 3
11 11 11 01 -> -3
```
符号位最高位为符号位0表示正数1表示负数。在位运算中符号位也参与运算。
**2. 按位非操作 ~**
```c
~ 1 = 0
~ 0 = 1
```
`~``num`的补码中的 0 和 1 全部取反0 变为 11 变为 0有符号整数的符号位在 `~` 运算中同样会取反。
```c
00 00 01 01 -> 5
~
---
11 11 10 10 -> -6
11 11 10 11 -> -5
~
---
00 00 01 00 -> 4
```
**3. 按位与操作 &**
```c
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
```
只有两个对应位都为 1 时才为 1
```c
00 00 01 01 -> 5
&
00 00 01 10 -> 6
---
00 00 01 00 -> 4
```
**4. 按位或操作 |**
```c
1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0
```
只要两个对应位中有一个 1 时就为 1
```c
00 00 01 01 -> 5
|
00 00 01 10 -> 6
---
00 00 01 11 -> 7
```
**5. 按位异或操作 ^**
```c
1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0
```
只有两个对应位不同时才为 1
```c
00 00 01 01 -> 5
^
00 00 01 10 -> 6
---
00 00 00 11 -> 3
```
异或操作的性质:满足交换律和结合律
```c
A: 00 00 11 00
B: 00 00 01 11
A^B: 00 00 10 11
B^A: 00 00 10 11
A^A: 00 00 00 00
A^0: 00 00 11 00
A^B^A: = A^A^B = B = 00 00 01 11
```
**6. 按位左移操作 <<**
`num << i``num`的二进制表示向左移动`i`位所得的值。
```c
00 00 10 11 -> 11
11 << 3
---
01 01 10 00 -> 88
```
**7. 按位右移操作 >>**
`num >> i``num`的二进制表示向右移动`i`位所得的值。
```c
00 00 10 11 -> 11
11 >> 2
---
00 00 00 10 -> 2
```
**8. 利用位运算实现快速计算**
通过 `<<``>>` 快速计算2的倍数问题。
```
n << 1 -> 计算 n*2
n >> 1 -> 计算 n/2负奇数的运算不可用
n << m -> 计算 n*(2^m),即乘以 2 的 m 次方
n >> m -> 计算 n/(2^m),即除以 2 的 m 次方
1 << n -> 2^n
```
通过 `^` 快速交换两个整数。
```c
a ^= b
b ^= a
a ^= b
```
通过 `a & (-a)` 快速获取`a`的最后为 1 位置的整数。
```c
00 00 01 01 -> 5
&
11 11 10 11 -> -5
---
00 00 00 01 -> 1
00 00 11 10 -> 14
&
11 11 00 10 -> -14
---
00 00 00 10 -> 2
```
**9. 利用位运算实现整数集合**
一个数的二进制表示可以看作是一个集合0 表示不在集合中1 表示在集合中)。
比如集合 `{1, 3, 4, 8}`,可以表示成 `01 00 01 10 10` 而对应的位运算也就可以看作是对集合进行的操作。
元素与集合的操作:
```
a | (1<<i) -> 把 i 插入到集合中
a & ~(1<<i) -> 把 i 从集合中删除
a & (1<<i) -> 判断 i 是否属于该集合(零不属于,非零属于)
```
集合之间的操作:
```c
a -> ~a
a b -> a & b
a b -> a | b
a b -> a & (~b)
```
## 2 只出现一次的数字
> - 题号136
> - 难度:简单
> - https://leetcode-cn.com/problems/single-number/
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
<b>说明</b>
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
<b>示例 1</b>:
```c
: [2,2,1]
: 1
```
<b>示例 2</b>:
```c
: [4,1,2,1,2]
: 4
```
**思路:** 利用"异或"操作的性质。
```c
A: 00 00 11 00
B: 00 00 01 11
A^A: 00 00 00 00
A^0: 00 00 11 00
A^B^A: = A^A^B = B = 00 00 01 11
```
**C# 实现**
- 状态:通过
- 16 / 16 个通过测试用例
- 执行用时: 144 ms, 在所有 C# 提交中击败了 91.76% 的用户
- 内存消耗: 25.4 MB, 在所有 C# 提交中击败了 11.39% 的用户
```c
public class Solution
{
public int SingleNumber(int[] nums)
{
int result = 0;
for (int i = 0; i < nums.Length; i++)
{
result ^= nums[i];
}
return result;
}
}
```
**Python 实现**
- 执行结果:通过
- 执行用时44 ms, 在所有 Python3 提交中击败了 84.17% 的用户
- 内存消耗15.3 MB, 在所有 Python3 提交中击败了 5.26% 的用户
```python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
result = 0
for item in nums:
result ^= item
return result
```
## 3 2的幂
> - 题号231
> - 难度:简单
> - https://leetcode-cn.com/problems/power-of-two/
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
<b>示例 1</b>:
```c
: 1
: true
: 2^0 = 1
```
<b>示例 2</b>:
```c
: 16
: true
: 2^4 = 16
```
<b>示例 3</b>:
```c
: 218
: false
```
**思路:** 利用"异或"操作的性质。
```c
A: 00 00 11 00
A^A: 00 00 00 00
```
**C# 语言**
- 状态:通过
- 1108 / 1108 个通过测试用例
- 执行用时: 36 ms, 在所有 C# 提交中击败了 100.00% 的用户
- 内存消耗: 14.7 MB, 在所有 C# 提交中击败了 100.00% 的用户
```c
public class Solution
{
public bool IsPowerOfTwo(int n)
{
if (n < 0)
return false;
for (int i = 0; i < 32; i++)
{
int mask = 1 << i;
if ((n ^ mask) == 0)
return true;
}
return false;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时44 ms, 在所有 Python3 提交中击败了 51.91% 的用户
- 内存消耗13.6 MB, 在所有 Python3 提交中击败了 6.25% 的用户
```python
class Solution:
def isPowerOfTwo(self, n: int) -> bool:
for i in range(32):
mask = 1 << i
if n ^ mask == 0:
return True
return False
```
## 4 只出现一次的数字 III
> - 题号260
> - 难度:中等
> - https://leetcode-cn.com/problems/single-number-iii/
给定一个整数数组 nums其中恰好有两个元素只出现一次其余所有元素均出现两次。找出只出现一次的那两个元素。
**示例 :**
```c
: [1,2,1,3,2,5]
: [3,5]
```
**注意:**
1. 结果输出的顺序并不重要,对于上面的例子, [5, 3] 也是正确答案。
2. 你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
**思路:** 利用"异或"操作的性质。
通过异或操作 `^` 去除掉恰好重复出现两次的元素,这时得到两个只出现一次整数的异或结果`different`
```c
A: 00 00 11 00
B: 00 00 01 11
A^B: 00 00 10 11
B^A: 00 00 10 11
A^A: 00 00 00 00
A^0: 00 00 11 00
A^B^A: = A^A^B = B = 00 00 01 11
```
获取`different`二进制中最后一位1通过该位可以把这两个数分离出来这两个数在该位是不同的。也即通过该位把 `nums` 分成了两组,该位是 1 的一组,该位是 0 的一组,然后求两组中只出现一次的整数。
通过 `a & (-a)` 快速获取`a`的最后为 1 位置的整数。
```c
00 00 01 01 -> 5
&
11 11 10 11 -> -5
---
00 00 00 01 -> 1
00 00 11 10 -> 14
&
11 11 00 10 -> -14
---
00 00 00 10 -> 2
```
**C# 语言**
- 执行结果:通过
- 执行用时280 ms, 在所有 C# 提交中击败了 83.33% 的用户
- 内存消耗31.2 MB, 在所有 C# 提交中击败了 100.00% 的用户
```c
public class Solution
{
public int[] SingleNumber(int[] nums)
{
if (nums.Length < 2)
return new int[2];
int different = 0;
for (int i = 0; i < nums.Length; i++)
{
different ^= nums[i];
}
different &= -1 * different;
int num1 = 0, num2 = 0;
for (int i = 0; i < nums.Length; i++)
{
if ((different & nums[i]) == 0)
{
num1 ^= nums[i];
}
else
{
num2 ^= nums[i];
}
}
return new int[] { num1, num2 };
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时44 ms, 在所有 Python3 提交中击败了 83.70% 的用户
- 内存消耗14.8 MB, 在所有 Python3 提交中击败了 33.33% 的用户
```python
class Solution:
def singleNumber(self, nums: List[int]) -> List[int]:
if len(nums) < 2:
return []
different = 0
for num in nums:
different ^= num
different &= -1 * different
num1, num2 = 0, 0
for num in nums:
if (num & different) == 0:
num1 ^= num
else:
num2 ^= num
return [num1, num2]
```
## 5 子集
> - 题号78
> - 难度:中等
> - https://leetcode-cn.com/problems/subsets/
给定一组 不含重复元素 的整数数组 nums返回该数组所有可能的子集幂集
<b>说明</b>:解集不能包含重复的子集。
<b>示例</b>:
```c
: nums = [1,2,3]
:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
```
**思路:** 利用整数集合的思路。
`{1,2,3}`为例,三个数,共`2^3`个子集。
```c
000 -> []
100 -> [1]
101 -> [1,3]
110 -> [1,2]
111 -> [1,2,3]
...
```
**C# 语言**
- 状态:通过
- 10 / 10 个通过测试用例
- 执行用时: 348 ms, 在所有 C# 提交中击败了 97.80% 的用户
- 内存消耗: 29.5 MB, 在所有 C# 提交中击败了 6.67% 的用户
```c
public class Solution
{
public IList<IList<int>> Subsets(int[] nums)
{
IList<IList<int>> result = new List<IList<int>>();
int count = nums.Length;
for (int i = 0; i < 1 << count; i++)
{
IList<int> item = new List<int>();
for (int j = 0; j < count; j++)
{
int mask = 1 << j;
if ((mask & i) != 0)
item.Add(nums[j]);
}
result.Add(item);
}
return result;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时40 ms, 在所有 Python3 提交中击败了 63.08% 的用户
- 内存消耗13.8 MB, 在所有 Python3 提交中击败了 5.72% 的用户
```python
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
count = len(nums)
result = []
for i in range(1 << count):
item = []
for j in range(count):
mask = 1 << j
if (mask & i) != 0:
item.append(nums[j])
result.append(item)
return result
```
## 6 Pow(x, n)
> - 题号50
> - 难度:中等
> - https://leetcode-cn.com/problems/powx-n/
实现 `pow(x, n)` ,即计算 x 的 n 次幂函数。
**示例 1:**
```c
: 2.00000, 10
: 1024.00000
```
**示例 2:**
```c
: 2.10000, 3
: 9.26100
```
**示例 3:**
```c
: 2.00000, -2
: 0.25000
: 2-2 = 1/22 = 1/4 = 0.25
```
**示例 4:**
```c
: 1.00000, -2147483648
: 1.00000
```
**说明:**
- -100.0 < x < 100.0
- n 是 32 位有符号整数,其数值范围是 [2^31, 2^31 1] 。
**思路**:利用快速幂法。
假设我们要求`a^b`,那么`b`可以拆成二进制表示,例如当`b = 5`5的二进制是0101`5 = 2^3×0 + 2^2×1 + 2^1×0 + 2^0×1`,因此,我们将`a^5`转化为算 `a^(2^3×0 + 2^2×1 + 2^1×0 + 2^0×1)`,即`a^(2^0) × a^(2^2)`
![](https://img-blog.csdnimg.cn/20200419235913493.png)
我们先算出所有2的幂然后在算出所有x的2的幂次方。再把n拆成二进制把二进制当中对应位置是1的值乘起来就得到了结果。这套方法称为 **快速幂法**
**C# 实现**
- 执行结果:通过
- 执行用时56 ms, 在所有 C# 提交中击败了 51.87% 的用户
- 内存消耗15.1 MB, 在所有 C# 提交中击败了 50.00% 的用户
```c
public class Solution
{
public double MyPow(double x, int n)
{
int neg = n < 0 ? -1 : 1;
long g = Math.Abs((long)n);
double[] d = new double[32];
d[0] = x;
for (int i = 1; i < 32; i++)
{
d[i] = d[i - 1] * d[i - 1];
}
double result = 1.0d;
for (int i = 0; i < 32; i++)
{
int mask = 1 << i;
if ((mask & g) != 0)
{
result *= d[i];
}
}
return neg != -1 ? result : 1.0 / result;
}
}
```
注意:++`long g = Math.Abs((long)n);`需要把n转换成long因为`Math.Abs(int.MinValue)`会产生溢出错误++。
**Python 实现**
- 执行结果:通过
- 执行用时36 ms, 在所有 Python3 提交中击败了 75.02% 的用户
- 内存消耗13.8 MB, 在所有 Python3 提交中击败了 8.33% 的用户
```python
class Solution:
def myPow(self, x: float, n: int) -> float:
neg = -1 if n < 0 else 1
n = abs(n)
d = list()
d.append(x)
for i in range(1, 32):
d.append(d[-1] * d[-1])
result = 1.0
for i in range(32):
mask = 1 << i
if (mask & n) != 0:
result *= d[i]
return result if neg != -1 else 1.0 / result
```
## 7 只出现一次的数字 II
> - 题号137
> - 难度:中等
> - https://leetcode-cn.com/problems/single-number-ii/
给定一个 非空 整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
**说明:**
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
**示例 1:**
```c
: [2,2,3,2]
: 3
```
**示例 2:**
```c
: [0,1,0,1,0,1,99]
: 99
```
**思路:**
初始`result = 0`,将每个数想象成 32 位的二进制对于每一位的二进制的1累加起来必然是`3N`或者`3N + 1`(出现3次和1次)`3N`代表目标值在这一位没贡献,`3N + 1`代表目标值在这一位有贡献(=1然后将所有有贡献的位记录到`result`中。这样做的好处是如果题目改成`k`个一样,只需要把代码改成`count % k`即可,很通用并列去找每一位。
**C# 语言**
- 执行结果:通过
- 执行用时112 ms, 在所有 C# 提交中击败了 91.53% 的用户
- 内存消耗25.2 MB, 在所有 C# 提交中击败了 100.00% 的用户
```c
public class Solution
{
public int SingleNumber(int[] nums)
{
int result = 0;
for (int i = 0; i < 32; i++)
{
int mask = 1 << i;
int count = 0;
for (int j = 0; j < nums.Length; j++)
{
if ((nums[j] & mask) != 0)
{
count++;
}
}
if (count % 3 != 0)
{
result |= mask;
}
}
return result;
}
}
```
**Python 语言**
```python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
result = 0
for i in range(32):
mask = 1 << i
count = 0
for num in nums:
if num & mask != 0:
count += 1
if count % 3 != 0:
result |= mask
return result
```
以上 Python 代码与 C# 代码逻辑完全一致,但提交时报错,错误信息如下:
```c
[-2,-2,1,1,-3,1,-3,-3,-4,-2]
4294967292
-4
```
我们发现:
```c
-4 1111 1111 1111 1111 1111 1111 1111 1100
```
如果不考虑符号位
```c
1111 1111 1111 1111 1111 1111 1111 1100 -> 4294967292
```
是不是很坑C++C#Java等语言的整型是限制长度的byte 8位int 32位long 64位但 Python 的整型是不限制长度的即不存在高位溢出所以当输出是负数的时候会导致认为是正数因为它把32位有符号整型认为成了无符号整型真是坑。
我们对以上的代码进行修改,加入判断条件 `if result > 2 ** 31-1:` 超过32位整型的范围就表示负数了`result -= 2 ** 32`,即可得到对应的负数。
- 执行结果:通过
- 执行用时96 ms, 在所有 Python3 提交中击败了 19.00% 的用户
- 内存消耗14.8 MB, 在所有 Python3 提交中击败了 25.00% 的用户
```python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
result = 0
for i in range(32):
mask = 1 << i
count = 0
for num in nums:
if num & mask != 0:
count += 1
if count % 3 != 0:
result |= mask
if result > 2 ** 31-1:
result -= 2 ** 32
return result
```
## 8 格雷编码
> - 题号89
> - 难度:中等
> - https://leetcode-cn.com/problems/gray-code/
格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。
给定一个代表编码总位数的非负整数 n打印其格雷编码序列。格雷编码序列必须以 0 开头。
<b>示例 1</b>:
```c
: 2
: [0,1,3,2]
:
00 - 0
01 - 1
11 - 3
10 - 2
n
[0,2,3,1]
00 - 0
10 - 2
11 - 3
01 - 1
```
<b>示例 2</b>:
```c
: 0
: [0]
: 0
n 2^n
n = 0 2^0 = 1
n = 0 [0]
```
**思路:**
![雷格码](https://img-blog.csdnimg.cn/20190915115931873.png)
由 n 位推导 n+1 位结果时n+1 位结果包含 n 位结果,同时包含 n 位结果中在高位再增加一个位 1 所形成的令一半结果,但是这一半结果需要与前一半结果镜像排列。
**C# 语言**
- 状态:通过
- 12 / 12 个通过测试用例
- 执行用时: 296 ms, 在所有 C# 提交中击败了 95.83% 的用户
- 内存消耗: 24.8 MB, 在所有 C# 提交中击败了 16.67% 的用户
```c
public class Solution
{
public IList<int> GrayCode(int n)
{
IList<int> lst = new List<int>();
lst.Add(0);
for (int i = 1; i <= n; i++)
{
for (int j = lst.Count - 1; j >= 0; j--)
{
int item = lst[j] + (1 << i - 1);
lst.Add(item);
}
}
return lst;
}
}
```
**Python 语言**
- 执行结果:通过
- 执行用时44 ms, 在所有 Python3 提交中击败了 45.92% 的用户
- 内存消耗13.8 MB, 在所有 Python3 提交中击败了 20.00% 的用户
```python
class Solution:
def grayCode(self, n: int) -> List[int]:
lst = [0]
for i in range(1, n + 1):
count = len(lst)
for j in range(count - 1, -1, -1):
lst.append(lst[j] + (1 << i - 1))
return lst
```
注意:运算符的优先级
- 一元运算符优于二元运算符。如正负号。
- 先算术运算,后移位运算,最后位运算。例如 1 << 3 + 2 & 7等价于 (1 << (3 + 2)) & 7
- 逻辑运算最后结合

View File

@@ -0,0 +1,17 @@
## 开源贡献者
- 胡联粤:高级算法工程师
- Githubgithub.com/heitao5200
- CSDNblog.csdn.net/heitao5200
- 王嘉鹏:宁大研究生
- 知乎https://zhuanlan.zhihu.com/c_1221027352425451520
- CSDNhttps://blog.csdn.net/qq_29027865
- githubhttps://github.com/ditingdapeng
- 张楚:中科大、服务端研发工程师
- Githubhttps://github.com/YaxeZhang
- 马燕鹏:华北电力大学
- 微信公众号LSGO软件技术团队
- CSDNhttps://lsgogroup.blog.csdn.net/
- 韩绘锦:华北电力大学
- CSDNhttps://blog.csdn.net/weixin_45569785
- 姚行志:华北电力大学
- CSDNhttps://blog.csdn.net/soulmate______