本篇记录 LeetCode 算法部分第 16-20 题。

3Sum Closest

第 16 题 3Sum Closest

给定一个包含 n 个整型数的数组 S,找出 S 中的三个数,使得三者求和的结果和目标值最接近。返回求和结果,假定 S 中一定存在唯一解。 举例:数组 S = { -1 2 1 -4 },目标值 target = 1。最接近目标值的求和结果为 (-1 + 2 + 1 = 2)

这题是第 15 题的延伸。沿用前一题的思路,先对数组进行排序,取 a(i) + a(i+k) + a(n) 求和,如果结果和目标值一致,则直接将求和结果返回;如果结果大于目标值,则表明需要减小下标 n 的值,逐次减小,每次比较当前求和结果与目标值的差值和前一次求和比较的差值,取绝对值较小的保留,同时保留当前的求和结果;如果结果小于目标值,则需要增大下标 (i+k)。Java 代码如下

// ThreeSumClosest.java v1.0
public class Solution {
    public int threeSumClosest(int[] nums, int target) {
        int sum = Integer.MAX_VALUE;
        int diff = Integer.MAX_VALUE;
        int count = nums.length;
        Arrays.sort(nums);
        for (int i = 0; i < count - 2; i++) {
            int j = i + 1, k = count - 1;
            while (j < k) {
                int curSum = nums[i] + nums[j] + nums[k];
                int curDiff = curSum - target;
                if (curDiff == 0) return curSum;
                diff = Math.abs(diff) < Math.abs(curDiff) ? diff : curDiff;
                sum = target + diff;
                if (curDiff > 0) {
                    k--;
                    while (j < k && nums[k] == nums[k + 1]) k--;
                } else {
                    j++;
                    while (j < k && nums[j] == nums[j - 1]) j++;
                }
            }
        }
        return sum;
    }
}
Status Tests Run Time Language
Accepted 120 / 120 13 ms Java
// ThreeSumClosest.java v1.1
public class Solution {
    public int threeSumClosest(int[] nums, int target) {
        int sum = Integer.MAX_VALUE;
        int diff = Integer.MAX_VALUE;
        int count = nums.length;
        Arrays.sort(nums);
        for (int i = 0; i < count - 2;) {
            int j = i + 1, k = count - 1;
            while (j < k) {
                int curSum = nums[i] + nums[j] + nums[k];
                int curDiff = Math.abs(curSum - target);
                if (curDiff == 0) return curSum;
                if (curDiff < diff) {
                    diff = curDiff;
                    sum = curSum;
                }
                if (curSum > target) {
                    k--;
                    while (j < k && nums[k] == nums[k + 1]) k--;
                } else {
                    j++;
                    while (j < k && nums[j] == nums[j - 1]) j++;
                }
            }
            i++;
            while (i < count - 2 && nums[i] == nums[i - 1]) i++;
        }
        return sum;
    }
}
Status Tests Run Time Language
Accepted 120 / 120 11 ms Java

Python 代码

# 3sum_closest.py
class Solution(object):
    def threeSumClosest(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        res, diff = sys.maxsize, sys.maxsize
        count = len(nums)
        nums.sort()
        for i in range(count - 2):
            if i > 0 and i < count -2 and nums[i] == nums[i - 1]:
                continue
            j, k = i + 1, count - 1
            while j < k:
                cur_sum = nums[i] + nums[j] + nums[k]
                cur_diff = cur_sum - target
                if cur_diff == 0:
                    return cur_sum
                diff = diff if abs(diff) < abs(cur_diff) else cur_diff
                res = target + diff
                if cur_diff > 0:
                    k -= 1
                    while j < k and nums[k] == nums[k + 1]:
                        k -= 1
                else:
                    j += 1
                    while j < k and nums[j] == nums[j - 1]:
                        j += 1
        return res
Status Tests Run Time Language
Accepted 120 / 120 148 ms Python

Letter Combinations of a Phone Number

第 17 题 Letter Combinations of a Phone Number

给定一个数字型的字符串,返回这些数字在手机九宫格键盘上所有可能表示的字母组合。 举例,输入字符串 “23”,输出结果:[ “ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf” ]

抽象的看这道题,其实就是排列组合。键盘上 “23” 按钮可能的输出结果就是 “abc” 和 “def” 两个字符串中字符的所有组合情况。从每个数字所代表的字符串中选取一个,并和下一个数字所代表的字符串中字符组合,逐个拼接,很显然,可以通过递归处理,递归深度为数字键的个数。

// LetterCombinationsOfaPhoneNumber.java
public class Solution {
    private static String[] keymap = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

    public List<String> letterCombinations(String digits) {
        List<String> res = new ArrayList<>();
        if (digits == null || digits.length() == 0) return res;
        this.combineLetters(digits, "", digits.length(), 0, res);
        return res;
    }

    private void combineLetters(String digits, String str, int len, int pos, List<String> list) {
        String key = keymap[digits.charAt(pos) - '2'];
        for (int i = 0; i < key.length(); i++) {
            if (pos == len - 1) list.add(str + key.charAt(i));
            else combineLetters(digits, str + key.charAt(i), len, pos + 1, list);
        }
    }
}
Status Tests Run Time Language
Accepted 25 / 25 1 ms Java

4Sum

第 18 题 4Sum

给定 n 个整型数组成的数组 S,从中找出所有可能的 4 个整数 a, b, c, d 使得 a + b + c + d = 目标值 target。要求去重。 如 S = [ 1, 0, -1, 0, -2, 2 ] target = 0,则结果为 [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]

LeetCode 第 15 题 3Sum 已经给出了数组中取 3 个数求和为零,本题为 4 个数求和为目标值。可以直接借用 3Sum 的算法,先选取数组中一个数,把剩下的数组元素和所需要的差值传递给 3Sum 方法,如果 3Sum 返回了有效结果,则把当前选取的数分别插入 3Sum 的结果中。代码如下:

// FourSum.java v1.0
public class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
        if (nums == null || nums.length < 4) return res;
        Arrays.sort(nums);
        int count = nums.length;
        for (int i = 0; i < count - 3; i++) {
            while (i > 0 && i < count - 3 && nums[i] == nums[i - 1]) i++;
            int diff = target - nums[i];
            List<List<Integer>> lists = this.threeSum(Arrays.copyOfRange(nums, i + 1, count), diff);
            if (lists.isEmpty()) continue;
            for (List<Integer> list : lists) {
                list.add(nums[i]);
                res.add(list);
            }
        }
        return res;
    }
    // 3Sum
    private List<List<Integer>> threeSum(int[] nums, int target) {
        List<List<Integer>> lists = new ArrayList<>();
        if (nums == null || nums.length < 3) return lists;
        int count = nums.length, i = 0;
        while (i < count - 2) {
            int j = i + 1;
            int k = count - 1;
            while (j < k) {
                int sum = nums[i] + nums[j] + nums[k];
                if (sum == target) {
                    List<Integer> list = new ArrayList<>();
                    list.add(nums[i]);
                    list.add(nums[j++]);
                    list.add(nums[k--]);
                    lists.add(list);
                    while (j < k && nums[j] == nums[j - 1]) j++;
                    while (j < k && nums[k] == nums[k + 1]) k--;
                } else if (sum < target) {
                    j++;
                    while (j < k && nums[j] == nums[j - 1]) j++;
                } else {
                    k--;
                    while (j < k && nums[k] == nums[k + 1]) k--;
                }
            }
            i++;
            while (i < count - 2 && nums[i] == nums[i - 1]) i++;
        }
        return lists;
    }
}
Status Tests Run Time Language
Accepted 282 / 282 71 ms Java

Remove Nth Node From End of List

第 19 题 Remove Nth Node From End of List

给定一个链表,移除倒数第 n 个节点,返回该链表的首节点。 例如链表 1 -> 2 -> 3 -> 4 -> 5n = 2,移除倒数第 2 节点后的链表为 1 -> 2 -> 3 -> 5 假定 n 是有效的。

普通链表是单向,假如是正向的移除第 n 个节点,很好做,但是反向的移除就需要动下脑筋。最直接的办法就是先遍历链表求其长度,减去 n 就是正向的节点位置,然后再做依次顺序遍历,找到要移除的节点。这种方法需要两次遍历。还有一种略微机智的方法,只需要做一次遍历,先让早起步的头指针移动 n 次,再让另一个慢起步的头指针开始移动,这样等早起步头指针到达最后一个节点的时候,后一个指针正好到达要移除的节点。

// RemoveNthNodeFromEndOfList.java
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode res = new ListNode(0);
        res.next = head;
        ListNode node1 = res, node2 = res;
        for (int i = 0; i < n; i++) node1 = node1.next;
        while (node1.next != null) {
            node1 = node1.next;
            node2 = node2.next;
        }
        node2.next = node2.next.next;
        return res.next;
    }
}
Status Tests Run Time Language
Accepted 207 / 207 1 ms Java

Valid Parentheses

第 20 题 Valid Parentheses

给出一个仅包含字符 '(', ')', '{', '}', '['']' 的字符串,判断它是否有效。 有效的字符串必须是闭合的,如 "()" {[()]}"()[]{}" 是有效的,而 "(]""([)]" 是无效的。

从有效字符串的形式 "()" {[()]}"()[]{}" 中可以看出,这很像是四则运算的中缀表达式。而四则运算的中缀表达式可以通过栈这种数据结构来存储。因为左半括号必定再其对应右半括号的前面,所以遍历到当前字符为左半括号时,将其入栈。遍历到右半括号时,将栈顶字符出栈,比较是否匹配。直到栈元素为空。

// ValidParentheses.java
public class Solution {
    public boolean isValid(String s) {
        char[] stack = new char[s.length()];
        int index = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(' || s.charAt(i) == '[' || s.charAt(i) == '{') {
                stack[index++] = s.charAt(i);
            } else if (s.charAt(i) == ')') {
                if (index == 0 || stack[--index] != '(') return false;
            } else if (s.charAt(i) == ']') {
                if (index == 0 || stack[--index] != '[') return false;
            } else {
                if (index == 0 || stack[--index] != '{') return false;
            }
        }
        return index == 0;
    }
}
Status Tests Run Time Language
Accepted 65 / 65 0 ms Java

下一篇:LeetCode 探险第五弹