LeetCode算法题5:双指针
文章目錄
- 前言
- 一、有序數(shù)組的平方
- 二、輪轉(zhuǎn)數(shù)組
- 三、移動零
- 四、兩數(shù)之和 II - 輸入有序數(shù)組
- 五、反轉(zhuǎn)字符串
- 六、反轉(zhuǎn)字符串中的單詞 III
- 七、鏈表的中間結(jié)點(diǎn)
- 八、刪除鏈表的倒數(shù)第 N 個(gè)結(jié)點(diǎn)
- 總結(jié)
前言
??????Leetcode算法系列:https://leetcode-cn.com/study-plan/algorithms/?progress=njjhkd2
??????簡單總結(jié)一下雙指針相關(guān)的算法題:
一、有序數(shù)組的平方
??????題目鏈接:https://leetcode-cn.com/problems/squares-of-a-sorted-array/
??????題目描述:給你一個(gè)按 非遞減順序 排序的整數(shù)數(shù)組 nums,返回 每個(gè)數(shù)字的平方 組成的新數(shù)組,要求也按 非遞減順序 排序。
??????思路1:因?yàn)橛锌赡艽嬖谪?fù)數(shù),所以從數(shù)組的兩端開始計(jì)算,因?yàn)閿?shù)組元素平方的最大值不是在開頭,就是在末尾,由此可得下面解法:
class Solution {public int[] sortedSquares(int[] nums) {int[] res=new int[nums.length];int start=0,end=nums.length-1;for(int ri=end;ri>=0;ri--){ //從后往前 從兩邊向中間收縮。int t1=nums[start]*nums[start];int t2=nums[end]*nums[end];if(t1<t2){res[ri]=t2;end--;}else{res[ri]=t1;start++;}}return res;} }??????相反的,如果想從最小值的兩邊開始擴(kuò)散則不太容易。因?yàn)樵谶@樣一個(gè)非降序排列的數(shù)組中不太好直接找到絕對值最小的那個(gè)值。那么可以考慮先對原數(shù)組求平方,然后再找最小值。由此得到思路2。
??????思路2:從最小值開始往兩邊擴(kuò)散有一種更好的方法:先對原數(shù)組求平方之后,再找到最小值然后擴(kuò)散即可。可參考算法如下:
public int[] sortedSquares(int[] nums) {int len=nums.length-1,i;for(i=0;i<=len;i++)nums[i]=nums[i]*nums[i];int minIndex=0;for(i=1;i<=len;i++)if(nums[minIndex]>nums[i])minIndex=i;int[] ans=new int[len+1];i=0;ans[i++]=nums[minIndex];int left=minIndex-1,right=minIndex+1;while(left>=0&&right<=len){if(nums[left]<nums[right]){ans[i++]=nums[left];left--;}else{ans[i++]=nums[right];right++;} }while(left>=0){ans[i++]=nums[left];left--;}while(right<=len){ans[i++]=nums[right];right++;}return ans;}??????這兩種做法的時(shí)間復(fù)雜度和空間復(fù)雜度都為O(n)。
二、輪轉(zhuǎn)數(shù)組
??????題目鏈接:https://leetcode-cn.com/problems/rotate-array/
??????題目描述:給你一個(gè)數(shù)組,將數(shù)組中的元素向右輪轉(zhuǎn) k 個(gè)位置,其中 k 是非負(fù)數(shù)。
??????思路:假設(shè)數(shù)組長度為 len,k=len 或 =0 時(shí),原數(shù)組不會產(chǎn)生變化,所以需要取模來避免不必要的運(yùn)算。 令 k = k%len 。有一種比較巧妙的解法是進(jìn)行兩次逆序,參考如下:
class Solution {public void rotate(int[] nums, int k) {int len=nums.length,k1=k%len;int tmp;int i=0,j=len-1;while(i<j){ // 逆序(第一次)tmp=nums[i];nums[i]=nums[j];nums[j]=tmp;i++;j--;}for(i=0,j=k1-1;i<j;i++,j--){ //分別逆序(左半部分第二次)tmp=nums[i];nums[i]=nums[j];nums[j]=tmp;}for(i=k1,j=len-1;i<j;i++,j--){ //分別逆序(右半部分第二次)tmp=nums[i];nums[i]=nums[j];nums[j]=tmp;}} }??????時(shí)間復(fù)雜度為O(n),空間復(fù)雜度為O(1)。
??????思路2:更直觀的,如果我們新建一個(gè)數(shù)組,這道題立馬就變得簡單,但是空間復(fù)雜度也變?yōu)镺(n)了。
public void rotate(int[] nums, int k) {int len=nums.length,k1=k%len; // 注意:k 個(gè)元素輪轉(zhuǎn)k次為其本身。if(k1==0)return;int index=len-k1; //index 為起始下標(biāo)。int[] tmp=Arrays.copyOf(nums,len);int i,j=0;for(i=index;i<len;i++)nums[j++]=tmp[i];for(i=0;i<index;i++)nums[j++]=tmp[i];}??????思路3:想象一下這個(gè)數(shù)組是一個(gè)循環(huán)數(shù)組(參考循環(huán)隊(duì)列),k 表示所有元素向右移動 k 次,這個(gè)時(shí)候最終元素的位置就需要對數(shù)組長度取模了,經(jīng)過一丟丟的運(yùn)算,下標(biāo)為 index 的數(shù)的最終位置為 (index+k)%len,len 為數(shù)組長度。參考算法如下(空間復(fù)雜度仍為O(n)):
public void rotate(int[] nums, int k) {int len=nums.length,k1=k%len;int[] tmp=Arrays.copyOf(nums,len);for(int i=0;i<len;i++){nums[(i+k1)%len]=tmp[i];}??????思路4:將 3 的空間復(fù)雜度優(yōu)化降低為O(1),缺點(diǎn)是算法會稍微顯得有些復(fù)雜,參考如下:
public void rotate(int[] nums, int k) {//注意:不重復(fù)的幾輪交替轉(zhuǎn)換一定會搞定,一定不會存在重復(fù)交換的情況。int len=nums.length,k1=k%len;int count=0;int indexNew,valueOld,valueNew;for(int i=0;count!=len;i++){ // i一定不會增加到len。indexNew=(i+k1)%len;valueOld=nums[i];valueNew=nums[indexNew];while(i!=indexNew){nums[indexNew]=valueOld;valueOld=valueNew;indexNew=(indexNew+k1)%len;valueNew=nums[indexNew];count++;}nums[i]=valueOld; //最后收尾。count++;}三、移動零
??????題目鏈接:https://leetcode-cn.com/problems/move-zeroes/
??????題目描述:給定一個(gè)數(shù)組 nums,編寫一個(gè)函數(shù)將所有 0 移動到數(shù)組的末尾,同時(shí)保持非零元素的相對順序。
??????請注意 ,必須在不復(fù)制數(shù)組的情況下原地對數(shù)組進(jìn)行操作。
??????思路1:從頭開始遍歷數(shù)組,當(dāng)遇到一個(gè) 0 時(shí),將0之后的所有元素均往前移動一位,最后一個(gè)數(shù)置為0;下一次再遇到 0,依然將此 0 之后的所有元素往前移動一位,也將最后一個(gè)數(shù)置為0。 算法的時(shí)間復(fù)雜度為O(n2)。參考算法:
public void moveZeroes(int[] nums) {int len=nums.length;int i,j,countZero=0; for(i=0;i<len-countZero;){if(nums[i]==0){countZero++;for(j=i;j<len-1;j++)nums[j]=nums[j+1];nums[j]=0;}elsei++; }}??????思路2:雙指針,首先,一個(gè)指針 a 標(biāo)記數(shù)組中出現(xiàn)的第一個(gè)零,另一個(gè)指針 b 標(biāo)記在 a 之后的第一個(gè)非零數(shù),因?yàn)榭赡苡羞B續(xù)的 0 存在,所以 b 并不總等于 a+1。
??????然后,重復(fù)執(zhí)行 “交換 a 和 b 所指向的值,再令 a++,此時(shí) a 所指向的元素必為0;增加 b 的值,直到它指向下一個(gè)非零數(shù)(或者指向數(shù)組尾,此時(shí)算法結(jié)束)”。時(shí)間復(fù)雜度為O(n)。參考算法:
??????思路2 也可以用下面的算法來實(shí)現(xiàn):
public void moveZeroes(int[] nums) {int indexNow = 0,indexNum = 0,len = nums.length;while(indexNum<m){if(nums[indexNum] != 0) nums[indexNow++] = nums[indexNum];++indexNum;}for(int i = indexNow; i < m; i++)nums[i] = 0;}四、兩數(shù)之和 II - 輸入有序數(shù)組
??????題目鏈接:https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/
??????題目描述:給你一個(gè)下標(biāo)從 1 開始的整數(shù)數(shù)組 numbers ,該數(shù)組已按 非遞減順序排列 ,請你從數(shù)組中找出滿足相加之和等于目標(biāo)數(shù) target 的兩個(gè)數(shù)。如果設(shè)這兩個(gè)數(shù)分別是 numbers[index1] 和 numbers[index2] ,則 1 <= index1 < index2 <= numbers.length 。
??????以長度為 2 的整數(shù)數(shù)組 [index1, index2] 的形式返回這兩個(gè)整數(shù)的下標(biāo) index1 和 index2。
??????你可以假設(shè)每個(gè)輸入 只對應(yīng)唯一的答案 ,而且你 不可以 重復(fù)使用相同的元素。
??????你所設(shè)計(jì)的解決方案必須只使用常量級的額外空間。
??????思路1:二分查找:從第一個(gè)數(shù)開始,在之后的數(shù)中查找另一個(gè)數(shù),使得二者之和等于target,存在即返回,否則遍歷數(shù)組直至結(jié)束。時(shí)間復(fù)雜度為O(nlogn)。參考算法:
public int[] twoSum(int[] numbers, int target) {for (int i = 0; i < numbers.length; ++i) {int low = i + 1, high = numbers.length - 1;while (low <= high) {int mid = (high - low) / 2 + low;if (numbers[mid] == target - numbers[i]) return new int[]{i + 1, mid + 1};else if (numbers[mid] > target - numbers[i]) high = mid - 1;else low = mid + 1;}}return new int[]{-1, -1};}??????思路2:雙指針,由于此數(shù)組為非遞減,令指針 index1 指向數(shù)組第一個(gè)元素,令指針 index2 指向數(shù)組第二個(gè)元素,當(dāng)這兩處元素之和大于 target 時(shí),只能減小 index2;小于 target 時(shí),只能增大 index1;等于 target 時(shí),算法結(jié)束。時(shí)間復(fù)雜度為O(n)。參考算法:
public int[] twoSum(int[] numbers, int target) {int index1=0,index2=numbers.length-1;int tmp;while(index1<index2){tmp=numbers[index1]+numbers[index2];if(tmp==target)return new int[]{index1+1,index2+1};else if(tmp<target)index1++;elseindex2--;}return null;}五、反轉(zhuǎn)字符串
??????題目鏈接:https://leetcode-cn.com/problems/reverse-string/
??????題目描述:編寫一個(gè)函數(shù),其作用是將輸入的字符串反轉(zhuǎn)過來。輸入字符串以字符數(shù)組 s 的形式給出。
??????不要給另外的數(shù)組分配額外的空間,你必須原地修改輸入數(shù)組、使用 O(1) 的額外空間解決這一問題。
??????思路:雙指針,首尾元素交換即可。時(shí)間復(fù)雜度為O(n)。參考算法:
public void reverseString(char[] s) {int index1=0,index2=s.length-1;char tmp;while(index1<index2){tmp=s[index1];s[index1]=s[index2];s[index2]=tmp;index1++;index2--;}}六、反轉(zhuǎn)字符串中的單詞 III
??????題目鏈接:https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/
??????題目描述:給定一個(gè)字符串 s ,你需要反轉(zhuǎn)字符串中每個(gè)單詞的字符順序,同時(shí)仍保留空格和單詞的初始順序。
??????思路:雙指針,一個(gè)指針每次指向字符串中單詞的開頭,另一個(gè)指針指向單詞的末尾,反轉(zhuǎn)即可。時(shí)間復(fù)雜度為O(n),空間復(fù)雜度為O(1)。參考算法:
public String reverseWords(String s) {char[] ss=s.toCharArray();int index1=0,index2;int len=ss.length;char tmp;for(int i=0;i<=len;i++){if(i==len||ss[i]==' '){ //將上面的代碼稍微優(yōu)化一下,減少了原來的代碼量。index2=i-1;while(index1<index2){tmp=ss[index2];ss[index2]=ss[index1];ss[index1]=tmp;index2--;index1++;}index1=i+1; }}return new String(ss); }七、鏈表的中間結(jié)點(diǎn)
??????題目鏈接:https://leetcode-cn.com/problems/middle-of-the-linked-list/
??????題目描述:給定一個(gè)頭結(jié)點(diǎn)為 head 的非空單鏈表,返回鏈表的中間結(jié)點(diǎn)。如果有兩個(gè)中間結(jié)點(diǎn),則返回第二個(gè)中間結(jié)點(diǎn)。
??????思路1:簡單的做法,遍歷兩次,第一次得到鏈表的長度;第二次返回鏈表的中間結(jié)點(diǎn)。參考算法:
public ListNode middleNode(ListNode head) {int len=0;ListNode tmp;for(tmp=head;tmp!=null;tmp=tmp.next)len++;int mid=len/2,i=0;for(tmp=head;i<mid;i++)tmp=tmp.next;return tmp;}??????思路2:雙指針,兩個(gè)指針之間的關(guān)系為:一個(gè)指針 index2 一次跑兩步,另一個(gè)指針 index1 一次跑一步。這里需要注意比如結(jié)點(diǎn)總數(shù)為 2 或者 3 時(shí),返回的中間節(jié)點(diǎn)是相同的。參考算法:
public ListNode middleNode(ListNode head) {ListNode index1,index2;index1=index2=head;while(index2!=null&&index2.next!=null){ //節(jié)點(diǎn)總數(shù)為3時(shí),index1仍為總數(shù)為2的結(jié)果,沒有發(fā)生改變。index2=index2.next.next;index1=index1.next;}return index1;}八、刪除鏈表的倒數(shù)第 N 個(gè)結(jié)點(diǎn)
??????題目鏈接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
??????題目描述:給你一個(gè)鏈表,刪除鏈表的倒數(shù)第 n 個(gè)結(jié)點(diǎn),并且返回鏈表的頭結(jié)點(diǎn)。
??????思路1:基于上題的經(jīng)驗(yàn),直接采用雙指針來做,僅需要修改兩個(gè)指針之間的關(guān)系為:保持兩個(gè)指針之間的距離恒定為 n。這樣,當(dāng)指針 index2 指向鏈表尾時(shí),index1 為待刪除結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn),然后進(jìn)行刪除操作。但是還需要考慮刪除第一個(gè)結(jié)點(diǎn)的情況,所以在剛開始時(shí),我創(chuàng)建了一個(gè) dummy 結(jié)點(diǎn),它的 next 指向頭節(jié)點(diǎn),這樣就方便多了。參考算法:
public ListNode removeNthFromEnd(ListNode head, int n) {ListNode dummy,index1,index2;dummy=new ListNode(-1,head);//dummy 結(jié)點(diǎn)index1=dummy;index2=head;int count=0;while(index2!=null){index2=index2.next;count++;if(count>n)index1=index1.next;}index1.next=index1.next.next;return dummy.next;}??????注,不管是通過計(jì)算鏈表長度的方式,還是通過建立輔助棧的解法,都沒有雙指針來的簡潔。
總結(jié)
??????無。
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的LeetCode算法题5:双指针的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LeetCode算法题4:二分查找及扩展
- 下一篇: 网络安全漏洞解析