Leetcode_双指针、滑动窗口

合并两个有序数组

题目描述

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

示例 1:

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:

输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:

输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。

提示:

nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-109 <= nums1[i], nums2[j] <= 109

进阶:你可以设计实现一个时间复杂度为 O(m + n) 的算法解决此问题吗?

解题

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int len1 = m - 1;
        int len2 = n - 1;
        int len = m + n - 1;
        while(len1 >= 0 && len2 >= 0) {
            nums1[len--] = nums1[len1] > nums2[len2] ? nums1[len1--] : nums2[len2--];
        }
        // 表示将nums2数组从下标0位置开始,拷贝到nums1数组中,从下标0位置开始,长度为len2+1
        System.arraycopy(nums2, 0, nums1, 0, len2 + 1);
    }
}

复杂性分析

  • 时间复杂度: O(m+n)
  • 空间复杂度: O(1)

找到字符串中所有字母异位词

1 题目描述

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指字母相同,但排列不同的字符串。

示例 1:

输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 "abc"的异位词。

示例 2:

输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 "ab"的异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的异位词。 起始索引等于 2 的子串是 “ab”, 它是 "ab"的异位词。

提示:

  • 1 <= s.length, p.length <= 3 * 104
  • s 和 p 仅包含小写字母

2 解题(Java)

滑动窗口+字符运算

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> res = new ArrayList<>();
        if (s.length() < p.length()) {
            return res;
        }
        int[] sArray = new int[26];
        int[] pArray = new int[26];

        for (int i=0; i<p.length(); i++) {
            sArray[s.charAt(i) - 'a']++;
            pArray[p.charAt(i) - 'a']++;
        }

        if (Arrays.equals(sArray,pArray)) {
            res.add(0);
        }

        for (int i=p.length(); i<s.length(); i++) {
            sArray[s.charAt(i-p.length()) - 'a']--;
            sArray[s.charAt(i) - 'a']++;
            if (Arrays.equals(sArray,pArray)) {
                res.add(i - p.length() + 1);
            }
        }

        return res;
    }
}

3 复杂性分析

  • 时间复杂度:O(n),for循环O(n),数组长度为常数,因此数组的比较也是常数级,总时间复杂度为O(n);
  • 空间复杂度:O(1);

移动零

1 题目描述

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:

  1. 必须在原数组上操作,不能拷贝额外的数组。
  2. 尽量减少操作次数。

2 解题(Java)

快排思想

class Solution {
	public void moveZeroes(int[] nums) {
		if(nums == null) return;
		int left = 0;
		for(int right = 0; right < nums.length; right++) {
			if(nums[right] != 0) {
				int tmp = nums[right];
				nums[right] = nums[left];
				nums[left++] = tmp;
			}
		}
	}
}	

3 复杂性分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

滑动窗口的最大值

1 题目描述

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:
在这里插入图片描述

提示:

你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。

2 解题(Java)

2.1 解题思路

在这里插入图片描述
本题难点: 如何在每次窗口滑动后,将 “获取窗口内最大值” 的时间复杂度从 O(k) 降低至 O(1);

回忆 包含min函数的栈,其使用 单调栈 实现了随意入栈、出栈情况下的 O(1) 时间获取 “栈内最小值” ,本题同理。

窗口对应的数据结构为 双端队列 ,本题使用 单调队列 即可解决以上问题。遍历数组时,每轮保证单调非严格递减队列 deque:

  1. deque 内仅包含窗口内的元素 ⇒ 每轮窗口滑动移除了元素 nums[left−1] ,如果上一个窗口[left-1,right-1]最大值deque.peekFirst()等于nums[left-1],还需删除deque的队头元素;
  2. deque 内的元素 非严格递减 ⇒ 每轮窗口滑动添加了元素 nums[right + 1],需将 deque 内所有 < nums[right + 1] 的元素删除;

2.2 算法流程

  1. 初始化: 双端队列deque ,结果列表res,数组长度 n;
  2. 滑动窗口: 左边界范围 left∈[1−k,n−k] ,右边界范围 right∈[0,n−1] ;
    1. 若 left > 0 且 队首元素 deque[0] == 被删除元素 nums[left - 1]:则队首元素出队;
    2. 删除 deque 内所有<nums[right] 的元素,以保持 deque 非严格递减;
    3. 将 nums[right] 添加至 deque 尾部;
    4. 若已形成窗口(left≥0 ):将窗口最大值(即队首元素 deque[0])添加至数组 res;
  3. 返回值: 返回结果列表 res ;

2.3 代码

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums.length == 0 || k == 0) return new int[0];
        Deque<Integer> deque = new LinkedList<>();
        int[] res = new int[nums.length - k + 1];
        for (int right=0,left=1-k; right < nums.length; left++, right++) {
            // 保持deque非严格递减
            while (!deque.isEmpty() && deque.peekLast() < nums[right]) {
                deque.pollLast();
            }
            deque.offer(nums[right]);
            if (left >= 0) {
                res[left] = deque.peek();// 记录窗口最大值
            }
            // 如果窗口[left,right]最大值恰好就是nums[left],右移之后最大值作废,所以删除队列头
            if (left >= 0 && deque.peek() == nums[left]) {
                deque.poll(); 
            }
        }
        return res;
    }
}

3 复杂性分析

  • 时间复杂度 O(N) : 其中 N 为数组 nums 长度;线性遍历 nums 占用 O(N);每个元素最多仅入队和出队一次,因此单调队列 deque 占用 O(2N) ;
  • 空间复杂度 O(k) : 双端队列 deque 中最多同时存储 k 个元素(即窗口大小);

*和为s的连续正数序列

1 题目描述

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

示例 1:

输入:target = 9
输出:[[2,3,4],[4,5]]

示例 2:

输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]

限制:

1 <= target <= 10^5

2 解题(Java)

2.1 解题思路

  1. 设连续正整数序列的左边界 i 和右边界 j ,构建滑动窗口从左到右滑动;
  2. 循环中,每轮判断滑动窗口内元素和s与目标值target的大小关系;
  3. 如果相等,记录结果并向右移动左边界i(尝试其他方案);如果大于target,向右移动左边界i(减小窗口内的元素和);如果小于target,向右移动右边界(增大窗口内的元素和);

2.2 算法流程

  1. 初始化:左边界left = 1,右边界right = 2,元素和sum = 3,结果列表res;

  2. 循环:当left >= right时跳出:

    • 如果sum > target:向右移动左边界left = left + 1,并更新元素和sum;
    • 如果sum < target:向右移动右边界right = right + 1,并更新元素和sum;
    • 如果sum = target;记录连续整数序列,并向右移动左边界 left = left + 1;
  3. 返回值:返回结果列表res;

2.3 代码

class Solution {
    public int[][] findContinuousSequence(int target) {
        int left = 1, right = 2, sum = 3;
        List<int[]> res = new LinkedList<>();
        while (left < right) {
            if (sum == target) {
                int[] ans = new int[right - left + 1];
                for (int i = left; i <= right; i++) {
                    ans[i - left] = i;
                }
                res.add(ans);
            }
            if (sum >= target) {
                sum -= left;
                left++;
            } else {
                right++;
                sum += right;
            }
        }
        return res.toArray(new int[][]{});
    }
}

3 复杂性分析

  • 时间复杂度 O(N): 其中 N = target,连续整数序列至少有两个数字,而 left < right 恒成立,因此至多循环 target( left, right 都移动到target/2),使用 O(N) 时间;
  • 空间复杂度O(1):除了答案数组外,变量 left , right , sum 使用常数大小的额外空间;

*最小覆盖子串

1 题目描述

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1

输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”

示例 2

输入:s = “a”, t = “a”
输出:“a”

提示

  • 1 <= s.length, t.length <= 10 ^ 5
  • s 和 t 由英文字母组成

进阶:你能设计一个在 o(n) 时间内解决此问题的算法吗?

2 解题(Java)

class Solution {
    public String minWindow(String s, String t) {
        Map<Character, Integer> tMap = new HashMap<>();
        Map<Character, Integer> windowMap = new HashMap<>();
        // 记录t中所有字符及其出现的次数
        for (char c : t.toCharArray()) {
            tMap.put(c, tMap.getOrDefault(c, 0) + 1);
        }
        int left = 0, right = 0;
        // 记录窗口中满足条件的字符个数
        int count = 0;
        // 记录最小覆盖子串的起始索引及长度
        int start = 0, minLength = Integer.MAX_VALUE;
        while (right < s.length()) {
            char c = s.charAt(right);
            // 判断取出的字符是否在t中
            if (tMap.containsKey(c)) {
                windowMap.put(c, windowMap.getOrDefault(c, 0) + 1);
                // 判断取出的字符在窗口中出现的次数是否与t中该字符的出现次数相同
                if (windowMap.get(c).equals(tMap.get(c))) {
                    count++;
                }
            }
            // 找到符合条件的子串后,尝试缩小窗口
            while (count == tMap.size()) {
                if (right - left + 1 < minLength) {
                    start = left;
                    minLength = right - left + 1;
                }
                char c1 = s.charAt(left);
                left++;
                if (tMap.containsKey(c1)) {
                    if (windowMap.get(c1).equals(tMap.get(c1))) {
                        count--;
                    }
                    windowMap.put(c1, windowMap.get(c1) - 1);
                }
            }
            // 尝试新方案
            right++;
        }
        return minLength == Integer.MAX_VALUE ? "" : s.substring(start, start + minLength);
    }
}

3 复杂性分析

  • 时间复杂度O(n):n为s的长度,线性遍历一次s;
  • 空间复杂度O(n):HashMap所占空间;

*调整数组顺序使奇数位于偶数前面

1 题目描述

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

示例:

输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。

2 解题(Java)

class Solution {
    public int[] exchange(int[] nums) {
        int left = 0, right = nums.length - 1;
        while (left < right) {
            while (left < right && (nums[left] & 1) == 1) {
                left++;
            }
            while (left < right && (nums[right] & 1) == 0) {
                right--;
            }
            int temp = nums[left];
            nums[left] = nums[right];
            nums[right] = temp;
        }
        return nums;
    }
}

3 复杂性分析

  • 时间复杂度 O(N): N 为数组 nums 长度,双指针 left, right 共同遍历整个数组;
  • 空间复杂度 O(1) : 双指针 left, right 使用常数大小的额外空间;

*和为s的两个数字

1 题目描述

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]

示例 2:

输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]

限制:

1 <= nums.length <= 10 ^ 5
1 <= nums[i] <= 10^6

2 解题(Java)

2.1 解题思路

  1. 利用哈希表可以通过遍历数组找到数字组合,时间和空间复杂度均为O(N);
  2. 由于本题的nums是排序数组,因此可以使用双指针法将空间复杂度降至O(1);

算法流程

  1. 初始化:双指针left,right分别指向数组nums的左右两端(对撞双指针);

  2. 循环搜索(当双指针相遇时跳出):

    • 计算s = nums[left] + nums[right];
    • 若s > target,right左移,即right = right - 1;
    • 若s < target,left右移,即left = left + 1;
    • 若s = target,立刻返回数组[nums[left], nums[right]];
  3. 返回空数组,表示不存在和为target的组合;

2.2 代码

class Solution {
    public int[] twoSum(int[] nums, int target) {
        // 定义左指针和右指针
        int left = 0, right = nums.length - 1;
        // 当双指针相遇时跳出
        while(left < right) {
            int s = nums[left] + nums[right];
            if(s < target) left++;
            else if(s > target) right--;
            else return new int[] { nums[left], nums[right] };
        }
        return new int[0];
    }
}

3 复杂性分析

  • 时间复杂度 O(N) : N 为数组 nums 的长度,双指针共同线性遍历整个数组;
  • 空间复杂度 O(1) : 变量 left, right 占用常数大小的额外空间;

*链表中倒数第k个节点

1 题目描述

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

示例

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.

2 解题(Java)

2.1 解题思路

使用双指针:

  1. 初始化: 前指针 former 、后指针 latter ,双指针都指向头节点 head;
  2. 构建双指针距离: 前指针former先向前走k步(结束后,双指针 former 和 latter 间相距 k 步);
  3. 双指针共同移动: 循环中,双指针former和latter每轮都向前走一步,直至former走过链表尾节点时跳出(跳出后,latter与尾节点距离为 k−1,即latter指向倒数第k个节点);
  4. 返回值: 返回latter即可;
  5. 考虑越界问题:former在前k次移动时,每次移动前,检查是否为null,如果是,则越界,返回null;

2.2 代码

/**
 * 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 former = head, latter = head;
        // former先走k步
        for(int i = 0; i < k; i++) {
        	// 考虑越界情况
			if(former == null) return null;
            former = former.next;
		}
        while(former != null) {
            former = former.next;
            latter = latter.next;
        }
        return latter;
    }
}

3 复杂性分析

  • 时间复杂度 O(N) : N 为链表长度,former 走了 N 步, latter 走了 (N−k) 步;
  • 空间复杂度 O(1): 双指针 former , latter 使用常数大小的额外空间;

*反转链表

1 题目描述

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

限制:

0 <= 节点个数 <= 5000

2 解题(Java)

2.1 解题思路

共定义3个指针:

temp暂存后继结点,cur修改引用指向,pre暂存当前结点,cur访问后继结点。

2.2 算法流程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3 代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur = head, pre = null;
        while(cur != null) {
            ListNode tmp = cur.next; // 暂存后继结点
            cur.next = pre;          // 修改引用指向
            pre = cur;               // 暂存当前结点
            cur = tmp;               // 访问下一结点
        }
        return pre;
    }
}

3 复杂性分析

  • 时间复杂度 O(N): 遍历链表使用线性大小时间。
  • 空间复杂度 O(1): 变量 pre 和 cur 使用常数大小额外空间。

*翻转单词顺序

1 题目描述

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。

示例 1:

输入: “the sky is blue”
输出: “blue is sky the”

示例 2:

输入: " hello world! "
输出: “world! hello”
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

示例 3:

输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

说明:

  • 无空格字符构成一个单词。
  • 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
  • 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

2 解题(Java)

2.1 解题思路

  1. 定义双指针left和right作为单词的左右索引边界,初始指向字符串s的末尾;
  2. 倒序遍历s,每确定一个单词边界,就将其添加至res;
  3. 最后,将res转成字符串,并去掉末尾的空格;

2.2 代码

class Solution {
    public String reverseWords(String s) {
        s = s.trim(); // 删除首尾空格
        int right = s.length() - 1, left = right;
        StringBuilder res = new StringBuilder();
        while(left >= 0) {
            while(left >= 0 && s.charAt(left) != ' ') left--; // 搜索首个空格
            res.append(s.substring(left + 1, right + 1) + " "); // 添加单词
            while(left >= 0 && s.charAt(left) == ' ') left--; // 跳过单词间空格
            right = left; // j 指向下个单词的尾字符
        }
        return res.toString().trim(); // 转化为字符串并返回
    }
}

(也可以利用 “字符串分割” 的内置函数split,再数组倒序的方法完成,面试不建议使用)

3 复杂性分析

  • 时间复杂度 O(N) : 其中 N 为字符串 s 的长度,线性遍历字符串使用O(N)时间;
  • 空间复杂度 O(N) : StringBuilder占用 O(N) 大小的额外空间;

*盛最多水的容器

1 题目描述

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器。

示例 1:
在这里插入图片描述

输入:[1,8,6,2,5,4,8,3,7]
输出:49 
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49

示例 2:

输入:height = [1,1]
输出:1

示例 3:

输入:height = [4,3,2,1,4]
输出:16

示例 4:

输入:height = [1,2,1]
输出:2

提示:

  • n = height.length
  • 2 <= n <= 3 * 10 ^ 4
  • 0 <= height[i] <= 3 * 10 ^ 4

2 解题(Java)

2.1 算法流程

设置双指针 left,right分别位于容器壁两端,根据规则移动指针,并且更新面积最大值 res,直到 left == right 时返回 res。

2.2 指针移动规则与证明

每次选定围成水槽两板高度h[left]和h[right]中的短板,向中间收窄 1 格。以下证明:

  1. 设每一状态下水槽面积为 S(left, right)(0 <= left < right < n),由于水槽的实际高度由两板中的短板决定,则可得面积公式 S(left, right) = min(h[left], h[right]) × (j - i);
  2. 在每一个状态下,无论长板或短板收窄 1 格,都会导致水槽底边宽度 -1:
    • 若向内移动短板,水槽的短板 min(h[left], h[right])可能变大,因此水槽面积 S(left, right)可能增大;
    • 若向内移动长板,水槽的短板 min(h[left], h[right])不变或变小,下个水槽的面积一定小于当前水槽面积;

因此,向内收窄短板可以获取面积最大值。

2.3 代码

class Solution {
    public int maxArea(int[] height) {
        int left = 0, right = height.length - 1, res = 0;
        while(left < right){
            res = height[left] < height[right] ?  Math.max(res, (right - left) * height[left++]) :  Math.max(res, (right - left) * height[right--]); 
        }
        return res;
    }
}

3 复杂性分析

  • 时间复杂度 O(N):双指针遍历一次数组;
  • 空间复杂度 O(1):双指针占用常数额外空间;

*三数之和

1 题目描述

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

2 解题(Java)

2.1 解题思路

  1. 暴力法搜索为 O(N^3) 时间复杂度,可通过双指针动态消去无效解来优化效率;
  2. 双指针法铺垫: 先将给定 nums 排序,时间复杂度为 O(NlogN);
  3. 三指针法思路: 固定 3 个指针中最左(最小)数字的指针 index,双指针 left,right 分设在数组索引 (index+1,len(nums)-1) 两端,通过双指针交替向中间移动,记录对于每个固定指针 index 的所有满足 nums[index] + nums[left] + nums[right] == 0 的 left,right 组合:
    • 当 nums[index] > 0 时直接break跳出:因为 nums[right] >= nums[left] >= nums[index] > 0,即 3 个数字都大于 0 ,在此固定指针 index 之后不可能再找到结果了;
    • 当 index > 0且nums[index] == nums[index - 1]时即跳过此元素nums[index]:nums[k - 1] 的组合已经包含了nums[k]的所有组合,本次双指针搜索只会得到重复组合;
    • left,right 分设在数组索引 (index+1, len(nums)-1) 两端,当left < right时循环计算s = nums[index] + nums[left] + nums[right],并按照以下规则执行双指针移动:
      1. 当s < 0时,left++并跳过所有重复的nums[left];
      2. 当s > 0时,right–并跳过所有重复的nums[right];
      3. 当s == 0时,记录组合[index, left, right]至res,执行left++ 和 right-- 并跳过所有重复的nums[left]和nums[right],防止记录到重复组合;

2.2 代码

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> res = new LinkedList<>();
        for (int index = 0; index < nums.length - 2; index++) {
            if (nums[index] > 0) break;
            if (index > 0 && nums[index] == nums[index - 1]) continue; 
            int left = index + 1, right = nums.length - 1;
            while (left < right) {
                int sum = nums[index] + nums[left] + nums[right];
                if (sum < 0) {
                    while (left < right && nums[left] == nums[++left]);
                } else if (sum > 0) {
                    while (left < right && nums[right] == nums[--right]);
                } else {
                    res.add(Arrays.asList(nums[index], nums[left], nums[right]));
                    while (left < right && nums[left] == nums[++left]);
                    while (left < right && nums[right] == nums[--right]);
                }
            }
        }
        return res;
    }
}

3 复杂性分析

  • 时间复杂度 O(N^2):其中外循环指针index遍历数组使用O(N)时间,内循环双指针left和right 遍历数组使用O(N)时间,共使用O(N ^ 2)时间;
  • 空间复杂度 O(1):指针占用常数大小的额外空间;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hellosc01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值