LeetCode双指针专题【算法】(未完成)
双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。
1. 有序数组的 Two Sum
167. Two Sum II - Input array is sorted (Easy)
Input: numbers={2, 7, 11, 15}, target=9
Output: index1=1, index2=2
题目描述:在有序数组中找出两个数,使它们的和为 target。
嵌套遍历。。突然不知道数组如何返回。。这里不能用初数化两个空间,因为返回的是两个不确定的值。用匿名返回。。
class Solution { public int[] twoSum(int[] numbers, int target) { int length = numbers.length;
for(int i=0;i<length;i++){ int current = numbers[i]; for(int j = i+1; j<length;j++){ if(current+numbers[j]==target) return new int[]{i+1,j+1};//直接用匿名返回 不用也行 外面定义一个全局int[] res=new int[2];则res[0]=i+1;res[1]=j+1; } } return null; } }
需要优化,内存消耗大
使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
- 如果两个指针指向元素的和 sum == target,那么得到要求的结果;
- 如果 sum > target,移动较大的元素,使 sum 变小一些;
- 如果 sum < target,移动较小的元素,使 sum 变大一些。
数组中的元素最多遍历一次,时间复杂度为 O(N)。只使用了两个额外变量,空间复杂度为 O(1)。
指针放两边,根据实际相加得的sum情况决定哪一个指针往中间收缩:
class Solution { public int[] twoSum(int[] numbers, int target) { int length = numbers.length; int i=0,j=length-1; while(i<j){ int sum = numbers[i]+numbers[j]; if(sum==target){ return new int[] {i+1,j+1}; }else if(sum>target){ j--; }else{ i++; } } return null; } }
2. 两数平方和
633. Sum of Square Numbers (Easy)
Input: 5
Output: True
Explanation: 1 * 1 + 2 * 2 = 5
题目描述:判断一个非负整数是否为两个整数的平方和。
第一题方法2一大一小的双指针的思路
class Solution { public boolean judgeSquareSum(int c) { int a =0; int b = (int)Math.sqrt(c);//我一开始想成c除以2了 结果提交后c=100000时不对会超时 int x = 0; while(a<=b){//2=1*1+1*1;漏了等号c=2不对 x=a*a+b*b; if(x == c){ return true; }else if(x>c){ //1001这个也给忘了。。 b--; }else{ a++; } } return false; } }
一个疑惑:只说整数,负整数也是整数啊。a为-1呢?
3. 反转字符串中的元音字符
345. Reverse Vowels of a String (Easy)
Given s = "leetcode", return "leotcede".
用String的replace方法和charAt方法?好复杂,编不出。。。
看答案的灵活思路,charAt用到了,replace不用因为可以输出新的就不必在原字符串上弄
答案:为了快速判断一个字符是不是元音字符,我们将全部元音字符添加到集合 HashSet 中,从而以 O(1) 的时间复杂度进行该操作。
class Solution { private final static HashSet <Character> vowels = new HashSet<>(Arrays.asList('a','e','i','o','u','A','E','I','O','U'));
//容器静态初始化的方法,就是直接加入一个List,但是这个List使用Arrays.asList方法得到的不可以改变长度。还有一个方法就是借助匿名类使用add方法。 public String reverseVowels(String s) { if(s==null) return null; //定义双指针,i为头索引,j为尾索引 int i=0,j=s.length()-1; //定义一个数据类型为char类型的数组,容量与字符串s长度相同 作为输出 char[] result = new char[s.length()];
while(i<=j){ //把原字符串s中i与j索引位置的字母取出来存放到ci,cj中!!! char ci=s.charAt(i); char cj=s.charAt(j); //如果头指针指向的不是元音字符,则i索引后移一位 if (!vowels.contains(ci)) { result[i++] = ci;//这里是把非set集合的字母依然放在原位上,但是i自身已经自增了!!!!!debug半天才明白。。。 //如果尾指针指向的不是元音字符,则j索引前移一位 } else if (!vowels.contains(cj)) {//当前边的已经找到了才来找后面的!!!! result[j--] = cj; //若i与j索引都指向元音字符,则i索引位置存放cj,j索引位置存放ci,并同时移动ij索引 } else {//上面两个都不执行,即都已经拿到了,则执行这里的交换!!!!! result[i++] = cj; result[j--] = ci; } } //最后将result数组转成字符串并返回 return new String(result); } }
java的数组没有直接的contains方法,需要遍历或转成集合;
字符串有contains方法,contains(s1):如果s1是该字符串的子串,返回true。还有equal方法
所以放字符串里应该也可以。
这一串if else if else执行顺序我迷茫了。。。还有就是,即便是最后两个也要交换,所以总是i<j,并不是middle
-------------------------------------------------------------------------------------------------------
知识点:以下总得执行一个
if (boolean) {
//如果boolean为false的话跳过这里代码 执行下面else if。
//如果boolean为true 的话执行完这里的代码,然后直接跳出,到方法 toast("你好")处
}
else if (boolean){
//如果boolean为false 的话 继续执行后面else if。
//如果boolean为true 的话执行完这里的代码,然后直接跳出,到方法 toast("你好")处
}
else if (boolean){}...
else {
//如果上面的所有if else if 都不满足的话,执行这里。
}
toast("你好");
————————————————
版权声明:本文为CSDN博主「帅气大果果」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_34471736/article/details/53406806
-----------------------------------------------------------------------------------------------------------------
4. 回文字符串
680. Valid Palindrome II (Easy)
Input: "abca"
Output: True
Explanation: You could delete the character 'c'.
题目描述:可以删除一个字符,判断是否能构成回文字符串。
前后指针,但是它可能有一个不确定在哪儿的干扰可以删除,而且只删除一次干扰,下次遇到不一样就直接false了。这个操作该加在哪儿判断呢?
class Solution { public boolean validPalindrome(String s) { int i=0,j=s.length()-1; while(i<j){ //遇到不一样的情况,有一次删除的机会。如何保证只有一次呢?因为进入下面的方法后之后就都就在下面方法里做判断了,最后也是在下面返回。
//如果没有进入下面的方法,则代表没有需要删除的干扰,一定是true的。
if(s.charAt(i)!=s.charAt(j)){ //尝试删除左端或右端然后再判断剩下的部分是否为回文串,任何一边成功就算成功 return isPalindrome(s,i+1,j)||isPalindrome(s,i,j-1);//有一次后这里直接返回了 } //两边字符一样,移动两端指针验证下一组字符 i++; j--; } return true; } public boolean isPalindrome(String s,int i,int j) { //该方法用来验证一个字符串是否为回文字符串,无删除机会,直接判断接下来的两边的情况,直到返回,不会再执行上面未完的移动 while(i<j){ if(s.charAt(i++)!=s.charAt(j--)) return false; } return true; } }
又是debug半天才反应过来如何只保证一次。。。遇到方法要点右边的蓝色箭头进去看。不然step就直接一个方法里往下走了。
5. 归并两个有序数组
88. Merge Sorted Array (Easy)
Input:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
Output: [1,2,2,3,5,6]
题目描述:把归并结果存到第一个数组上。
需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值。
我晕我两个方法全写错。。。。。
class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { // nums1 = Arrays.copy(nums2,0,nums2,m-1,m+n-1); 不是这么用的 // nums1.sort(); 没有这个方法。。。。 System.arraycopy(nums2, 0, nums1, m, n);//这才是把nums2从0开始,取n位,放到nums1的从m开始后的n位上。n是长度!! Arrays.sort(nums1); } }
关于数组的一些知识点:
int a[] = {3,4,5,0,0,0}; int[] b = Arrays.copyOfRange(a, 0, 3);//a数组从0索引到3索引上的元素复制 System.out.println(Arrays.toString(b));//[3, 4, 5] int[] aa = Arrays.copyOf(a,5);//a数组从头开始取5个长度复制 System.out.println(Arrays.toString(aa));//[3, 4, 5, 0, 0] int[] bb = a.clone();//a数组整体复制 System.out.println(Arrays.toString(bb));//[3, 4, 5, 0, 0, 0] //数组之间的复制整合!!不是前面的那种复制出另一个数组,而是作用在要操作的原数组上 System.arraycopy(b,0,a,3,2);//把b从0号索引的元素开始,取2个,放到a的3号索引开始的2个元素上 System.out.println(Arrays.toString(a));//[3, 4, 5, 3, 4, 0] System.out.println(Arrays.toString(b));//[3, 4, 5] 被复制的数组没有受影响 //a.sort()没有这个方法。。。。。。。。。 Arrays.sort(a);//数组排序 System.out.println(Arrays.toString(a));//[0, 3, 3, 4, 4, 5] Arrays.sort(bb,0,5);//数组中选择范围排序 System.out.println(Arrays.toString(bb));//[0, 0, 3, 4, 5, 0] //关于数组填充 int cc[] = {1,2,3,4,5,6}; int[] c = new int[6]; Arrays.fill(c,0,3,3); System.out.println(Arrays.toString(c));//[3, 3, 3, 0, 0, 0]
方法二:用双指针从前往后比,但不是输入的两个数组对比,鉴于nums1是要被改造的数组,所以我们复制一个他的备用数组来与nums2对比。
所以是需要新建一个数组空间
并不能用嵌套,有可能放前面的先挪,后面的就算是break出来让前面的挪动,但是后面的又得在for循环里从0开始,不对。他俩应该并列判断,取其中之1的情况。
而并不是前一个不动后一个疯狂动这种嵌套关系。
class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { int[] nums1_copy = Arrays.copyOfRange(nums1, 0, m); int p1 = 0,p2 = 0,p=0; while ((p1 < m) && (p2 < n)) nums1[p++] = (nums1_copy[p1] < nums2[p2]) ? nums1_copy[p1++] : nums2[p2++]; //收尾 if (p1 < m) System.arraycopy(nums1_copy, p1, nums1, p1 + p2, m + n - p1 - p2); if (p2 < n) System.arraycopy(nums2, p2, nums1, p1 + p2, m + n - p1 - p2); } }
方法三:双指针从后往前推,无需额外数组空间
class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { // two get pointers for nums1 and nums2 int p1 = m - 1; int p2 = n - 1; // set pointer for nums1 int p = m + n - 1; // while there are still elements to compare while ((p1 >= 0) && (p2 >= 0)) // compare two elements from nums1 and nums2 // and add the largest one in nums1 nums1[p--] = (nums1[p1] < nums2[p2]) ? nums2[p2--] : nums1[p1--]; // add missing elements from nums2 System.arraycopy(nums2, 0, nums1, 0, p2 + 1); } }
6. 判断链表是否存在环
141. Linked List Cycle (Easy)
使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。
快慢指针在环形上终将相遇。
/** * Definition for singly-linked list. * class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public boolean hasCycle(ListNode head) { if(head==null||head.next==null){ return false; } ListNode slow = head;//都快忘了链表指针怎么定义初始化了。。 ListNode fast = head.next; while (slow != fast) { if (fast == null || fast.next == null) {//这表示快的跑到终点了,也就是说没有环 return false; } slow = slow.next; fast = fast.next.next; } return true; } }
方法二:哈希表
思路
我们可以通过检查一个结点此前是否被访问过来判断链表是否为环形链表。常用的方法是使用哈希表。
算法
我们遍历所有结点并在哈希表中存储每个结点的引用(或内存地址)。如果当前结点为空结点 null(即已检测到链表尾部的下一个结点),那么我们已经遍历完整个链表,并且该链表不是环形链表。如果当前结点的引用已经存在于哈希表中(集合类有contains方法),那么返回 true(即该链表为环形链表)。
public class Solution { public boolean hasCycle(ListNode head) { if(head == null||head.next == null){ return false; } HashSet<ListNode> set = new HashSet<>(); while(head!=null){ if(set.contains(head)){ return true; }else{ set.add(head); } head=head.next; } return false;//若head为null从while里出来了直接返回false } }
作者:LeetCode
链接:https://leetcode-cn.com/problems/linked-list-cycle/solution/huan-xing-lian-biao-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
7. 最长子序列
524. Longest Word in Dictionary through Deleting (Medium)
Input:
s = "abpcplea", d = ["ale","apple","monkey","plea"]
Output:
"apple"
题目描述:删除 s 中的一些字符,使得它构成字符串列表 d 中的一个字符串,找出能构成的最长字符串。如果有多个相同长度的结果,返回字典序的最小字符串。
这题的关键就是怎么在字符串字典中找到那个对应的字符串。其实很简单。
只要利用两个指针i,j,一个指向s字符串,一个指向sstr字符串,每一次查找过程中,i依次后移。
若i,j对应的两个字符相等,则j后移,如果j可以移到sstr.length(),那么说明sstr中对应的字符s中都有,即s中删除一些字符后,可以得到sstr字符串。
最后一步就是比较当前的结果字符与找到的sstr字符,按照题目的需求来决定是否改变结果字符
public String findLongestWord(String s,List<String> d){ String str = "";//初始化一下 for (String sstr : d) {
//int j=0;写这里也可以 for (int i = 0, j = 0; i < s.length() && j < sstr.length(); i++) { if (s.charAt(i) == sstr.charAt(j)) j++; if (j == sstr.length()) { if (sstr.length() > str.length() || (sstr.length() == str.length() && str.compareTo(sstr) > 0))//长度最长||长度相等但顺序最小 str = sstr; } } } return str; }