5.雙指標技巧彙總
雙指標技巧彙總
快慢指標:主要解決連結串列中的問題,比如典型的判定連結串列中是否包含環
左右指標:主要解決陣列(或字串)中的問題,比如二分查詢
快慢指標
快慢指標一般都初始化指向連結串列的頭結點 head,前進時快指標 fast 在前,慢指標 slow 在後
1、判定連結串列中是否包含環
單鏈表的特點就是每個節點只知道下一個節點,所以一個指標的話無法判斷連結串列中是否含有環。
雙指標,如果不含環,跑的快的最終會遇到null;如果有環,快指標最終會超慢指標一圈,和慢指標相遇。
boolean hasCycle(ListNode head){ ListNode fast, slow; fast = slow = head; while(fast != null && fast.next != null){ fast = fast.next.next; slow = slow.next; if(fast == slow) return true; } return false; }
2、已知連結串列中含有環,返回這個環的起始位置
當快慢指標相遇時,讓其中任一個指標指向頭結點,然後讓它倆以相同的速度前進,再次相遇時所在的節點位置就是環開始的位置
ListNode detectCycle(ListNode head){ LsitNode fast, slow; fast = slow = head; while(fast != null && fast.next != null){ fast = fast.next.next; slow = slow.next; if(fast == slow) break; } slow = head; while(slow != fast){ fast = fast.next; slow = slow.next; } return slow }
第一次相遇時,慢指標 slow 走了 k 步,那麼快指標 fast 一定走了 2k 步
多走的 k 就是 fast 指標在環裡轉圈圈,k 是環長度的【整數倍】
設 相遇點距環的起點 距離為 m,那環的起點距頭結點 head 的距離 為 k - m
也就是說,從head 前進 k-m步就能到達環起點
巧的是,如果從相遇點繼續前進 k-m步,也恰好到達環起點。不管fast在環裡到底轉了幾圈,反正走 k步可以到相遇點,走k-m步一定就是走到環起點了
3、尋找連結串列的中點
快指標一次前進兩步,慢指標一次前進一步,當快指標到達連結串列盡頭時,慢指標就處於連結串列做的中間位置。
當連結串列的長度是奇數時,slow恰巧停在中點位置;如果長度是偶數,slow 最終的位置是中間偏右
ListNode middleNode(ListNode head){
ListNode fast,slow;
fast = slow = head;
while(fast-1=null&&fast.next!=null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
尋找連結串列中點的一個重要作用,是對連結進行歸併排序
陣列的歸併排序:求中點索引遞迴地把陣列二分,最後合併兩個有序陣列。對於連結串列,合併兩個有序連結串列很簡單,難點在於二分
4、尋找連結串列的倒數第 n 個元素
快指標先走n步,然後快慢指標同速前進,這樣當快指標走到連結串列末尾null時,慢指標所在的位置就是倒數第 n 個連結串列節點(n 不會超過連結串列長度)
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fast,slow;
fast =slow = head;
while(n-- > 0){//快指標先前進n步
fast = fast.next;
}
if(fast == null){//如果快指標走到頭了
return head.next;//說明倒數第n個節點就是第一個節點
}
while(fast!=null && fast.next != null){
//同步向前,快指標在前,與慢指標始終相差n
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;//slow.next 就是倒數第 n 個節點,刪除它
return head;
}
左右指標
左右指標在陣列中實際是指兩個索引值,一般初始化為 left = 0,right = nums.length - 1
1、二分查詢
int binarySearch(int[] nums,int target){
int left = 0;
int right = nums.length -1;
while(left<=right){
int mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] > target){
right = mid - 1;
}
}
return -1;
}
2、兩數之和
只要陣列有序,就應該想到雙指標技巧
public int[] twoSum(int[] nums, int target) {
int left = 0,right = nums.length - 1;
while(left < right){
int sum = nums[left] + nums[right];
if(sum == target){
return new int[]{left+1,right+1};
}else if(sum < target){
left++;//讓sum大一點
}else if(sum > target){
right--;
}
}
return new int[]{-1,-1};
}
3、反轉陣列
public void reverseString(char[] s) {
int left = 0;
int right = s.length-1;
while(left < right){
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
田忌賽馬?
策略:將齊王和田忌的馬按照戰鬥力排序,然後按照排名一一對比,如果田忌的馬能贏,那就比賽,如果贏不了,那就換個墊底的來換人頭,儲存例項
int n = nums1.length;
sort(nums1);//田忌的馬
sort(nums2);//齊王的馬
for(int i = n-1; i >=0; i--){
if(nums1[i] > nums2[i]){
//比得過,跟他比
}else{
//比不過,換個墊底的來換人頭
}
}
需要對兩個陣列排序,nums2中的元素順序不能改變,利用其他資料結構來輔助
雙指標,來處理 【送人頭】的情況
public int[] advantageCount(int[] nums1, int[] nums2) {
int n =nums1.length;
//給nums2 降序排序
PriorityQueue<int[]> maxpq = new PriorityQueue<>(
(int[] pair1, int[] pair2) -> {
return pair2[1] - pair1[1];
}
);
for(int i = 0; i<n; i++){
maxpq.offer(new int[]{i,nums2[i]});
}
//給 nums1 升序排序
Arrays.sort(nums1);
//nums1[left] 是最小值,nums1[right] 是最大值
int left = 0,right = n -1 ;
int[] res = new int[n];
while(!maxpq.isEmpty()){
int[] pair = maxpq.poll();
// maxval 是nums2 中的最大值, i 是對應索引
int i = pair[0],maxval = pair[1];
if(maxval < nums1[right]){
//如果 nums1[right] 能勝過 maxval ,那就自己上
res[i] = nums1[right];
right--;
}else{
//否則用最小值混一下,養精蓄銳
res[i] = nums1[left];
left++;
}
}
return res;
}
二叉堆和排序的複雜度O(nlogn)