常見演算法技巧之——雙指標思想
阿新 • • 發佈:2020-09-26
# 常見演算法技巧之——雙指標思想
雙指標思想是指設定兩個指標解決一些演算法問題。一般用的比較多的就是去解決陣列、連結串列類的問題,還有很耳熟能詳的二分查詢問題。本文將根據自己平時做題的總結以及在網上看到的其他大佬的總結講解來討論一下雙指標的使用技巧。本文會根據我平時做題實時更新。
## 快慢指標
雙指標的快慢指標用法是我最開始理解的第一種用法。快慢指標一般用來解決連結串列的問題多一些。設定一快一慢兩個指標```fast和slow```,初始化指向連結串列表頭。
### 1、計算連結串列的中點
給定一個連結串列,要求返回該連結串列的中點節點。
設定兩個快慢指標,都從頭節點開始,快指標每次前進兩個節點,慢指標每次前進一個節點,當快指標到達連結串列末尾的時候,慢指標剛好到達中點的節點。
下圖中藍色指標表示快指標,橙色指標表示慢指標。
![](https://img2020.cnblogs.com/blog/1786642/202009/1786642-20200916194614306-1695942872.png)
```java
//函式中最後的判斷return有問題,直接return slow即可,這樣寫是為了區分奇偶不同的區別
public LinkedList mid (LinkedList head) {
LinkedList fast = head;
LinkedList slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
if (fast == null) { //說明有偶數個節點,此時慢指標指向的是中點靠右的節點
return slow;
} else { //fast.next == null,說明有奇數個節點,此時慢指標指的恰好是中點
return slow;
}
}
```
### 2、判斷連結串列中是否有環
如果已知一個單鏈表的表頭,判斷該連結串列中是否含有一個環。通常單鏈表都是一個節點只指向下一個節點這樣的鏈式結構,如果只有一個指標,從頭節點開始一路next,如果碰到null則表示有環,但如果沒有環則會一直在環裡打轉,所以單指標很難解決這樣的問題。
設定雙指標,兩個指標一快一慢,按照不同的速度從頭節點開始往下遍歷,如果最後兩個指標相遇則表示有環,如果沒有相遇,而是快指標先到了null則表示沒有環。
```java
public boolean hasCycle (LinkedList head) {
LinkedList fast = head;
LinkedList slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) { //最後兩指標相遇,表示有環
return true;
}
}
return false; //若兩指標不相等,則說明快指標到了null
}
```
### 3、如果連結串列中有環,返回環的起始位置
如果已知一個連結串列中含有環,要找出環的位置,返回環的起始節點。
按照2中所說兩個指標相遇的時候說明這個連結串列中有環,那麼既然快指標速度是慢指標速度的二倍,那麼假設兩個指標第一次相遇的時候慢指標走了k步,則快指標就走了2k步,那麼快指標的路程減去慢指標的路程就是環的長度,即為k。
那此時假設環的起點距離兩指標的相遇點的距離為m,則從開始相遇點繼續往下走,直到再次達到**環起點**,一共走了```k-m```步,而之前假設慢指標走了k步,從開始到第一次相遇,慢指標是從連結串列表頭到第一次相遇點,走了k步,那麼慢指標從表頭到環的起點位置的距離也應該是```k-m```步(因為環起點距離相遇點是m步)。因此當兩指標第一次相遇的時候,將一個指標重新置到頭節點,然後兩個步調一致,同樣速度,每次前進一步,當再次相遇的時候,相遇位置就是環的起點。
![](https://img2020.cnblogs.com/blog/1786642/202009/1786642-20200916201136416-1406849428.png)
### 4、求連結串列中環的長度
這個很簡單,當兩個指標第一次相遇的時候,保持快指標不動,讓慢指標繼續跑,兩指標再次相遇的時候慢指標跑的距離就是環的長度。
### 5、求連結串列倒數第k個節點
先讓某個指標走k步,然後兩個指標同時走,當前面的指標走到末尾的時候,後面的指標呢恰好走到倒數第k個節點。
[leetcode中的例題](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/)
```java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast = head;
ListNode slow = head;
while (k > 0) {
--k;
fast = fast.next;
}
while (fast!=null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
```
## 左右指標
也有叫做碰撞指標的,通常需要一個有序的陣列,左右兩個指標其實就是指示陣列的下標,通常一個初始化在開頭,一個初始化在結尾。
### 1、二分查詢
這個是很經典的例子,在有序的陣列中查詢某個數(記為x),返回下標。
剛開始兩個指標一前一後,判斷兩指標最中間的元素是否為查詢的元素x,若是直接返回,如果不是且小於x,則將左指標移動到中間位置+1,即```(l+r)/2 + 1```,若大於x,則將右指標移動到中間位置-1。
```java
public int BinarySearch (int[] nums, int target) {
int l = 0, r = nums.length-1;
while (l < r) {
int mid = (l + r) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
r = mid - 1;
} else {
l = mid + 1;
}
}
return -1; //表示未找到
}
```
### 2、n數之和的問題
這裡以leetcode上的一個兩數之和的題為例為例,如果是三數之和可以轉化為一個數與兩數之和的和的問題。
#### [兩數之和](https://leetcode-cn.com/problems/two-sum/)
```
給定一個整數陣列 nums 和一個目標值 target,請你在該陣列中找出和為目標值的那 兩個 整數,並返回他們的陣列下標。
你可以假設每種輸入只會對應一個答案。但是,陣列中同一個元素不能使用兩遍。
示例:
給定 nums = [2, 7, 11, 15], target = 9
因為 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/two-sum
```
先對陣列排序,設定頭尾兩個指標,判斷相加之後與目標數的大小,如果相加之後大於目標,則移動右指標,讓右指標往左滑動,使得和更小,反之則讓左指標向右滑動,直到等於target。
但需要注意這道題給的並不一定是有序陣列,所以並不能使用這個方法,但可以作為一個引例來啟發我們,假設陣列是有序的。
```java
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
Arrays.sort(nums);
int l = 0, r = nums.length - 1;
while (l < r) {
if (nums[l] + nums[r] == target) {
return new int[] {l,r};
} else if (nums[l] + nums[r] < target) {
l = l + 1;
} else {
r = r - 1;
}
}
return new int[] {-1,-1};
}
```
#### [三數之和](https://leetcode-cn.com/problems/3sum/)
```
給你一個包含 n 個整數的陣列 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有滿足條件且不重複的三元組。
注意:答案中不可以包含重複的三元組。
示例:
給定陣列 nums = [-1, 0, 1, 2, -1, -4],
滿足要求的三元組集合為:
[
[-1, 0, 1],
[-1, -1, 2]
]
來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/3sum
```
正如前文所說,可以將三數之和轉化為兩數與一個數之和,因為要三數之和為0,先給定一個數,則另外兩個數之和就得等於這個數的相反數。則可以將這兩個數的和轉化為上述的兩數之和問題。
```java
class Solution {
public List