LeetCode 18四数之和、LeetCode 17电话号码的字母组合、LeetCode 347前k个高频元素、LeetCode 34在排序数组中查找元素的第一个和最后一个位置、39 组合总和

LeetCode 18四数之和

题目描述:
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

一、先对数组排序,之后两层循环分别枚举i和j=i+1,注意这里要使用剪枝和去重操作;后面的两个数使用双指针,left=j+1,right=length-1

剪枝

  • 当排序后前面四个数之和>target,说明往后循环i无论如何也不能满足条件了,此时break直接跳出
    在这里插入图片描述
  • 确定第一和第二个数之后,直接与length-1和length-2…下标的数相加,如果<target则说明length-2…前面的数也不能满足本次i循环,就continue进入下一次循环i+1

去重:因为每次的后一个都是前一个数+1,所以去重后不会影响结果【[1, 0, 0, 0] target=0这样】

  • 第一层循环
if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
  • 第二层循环
if(j > i + 1 && nums[j] == nums[j - 1]) continue;

二、双指针,如果==的时候,需要将左指针移动到不同的数,右指针也要移动到不同的数,方便得到的结果不同。

在这里插入图片描述

可通过完整代码:

public static List<List<Integer>> fourSum(int[] nums, int target) {
    List<List<Integer>> list = new ArrayList<>();
    int length = nums.length;
    if (length < 4) return list;
    Arrays.sort(nums);   // 先排序
    for (int i = 0; i < length - 3; i++) {   // <length能取到倒数第一个元素,<length-3能取到倒数第四个元素(前面还有3个元素没取到)
        if (i > 0 && nums[i] == nums[i - 1]) continue;   // 同一重循环中,如果当前元素与上一个元素相同,则跳过当前元素。【因为一次i之后的元素上一次通过j=i+1已经枚举过了】
        if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) break;   // 剪枝:如果前面4个之和【最小的4个和】>target,说明后面的循环i也没有机会==了
        if ((long) nums[i] + nums[length- 1] + nums[length- 2] + nums[length- 3] < target) continue;   // 本次nums[i]加上倒数三个【本次最大的4个和,淘汰本次后面的枚举】<target,说明本次i加上不是倒数三个的元素也不会==target了
        for (int j = i + 1; j < length - 2; j++) {
            if (j > i + 1 && nums[j] == nums[j - 1]) continue;
            if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) break;
            if ((long) nums[i] + nums[j] + nums[length- 1] + nums[length- 2] < target) continue;
            int left = j + 1, right = length- 1;
            while (left < right) {
                int sum = nums[i] + nums[j] + nums[left] + nums[right];
                if (sum == target) {
                    list.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                    while (left + 1 < right && nums[left] == nums[left + 1]) left++;
                    left++;
                    while (left < right - 1 && nums[right] == nums[right - 1]) right--;
                    right--;
                } else if (sum < target) {
                    left++;
                } else {
                    right--;
                }
            }
        }
    }
    return list;
}

LeetCode 17电话号码的字母组合

题目描述:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
在这里插入图片描述
示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]

示例 2:
输入:digits = “”
输出:[]

示例 3:
输入:digits = “2”
输出:[“a”,“b”,“c”]

一、首先使用哈希表存储每个数字对应的所有可能的字母,然后进行回溯操作。

在这里插入图片描述

二、回溯dfs函数,传入下标index=0,添加一个变为1,添加n个变为n。之后取digits.charAt(index)得到第一个数字,从map中取到对应的字母letters,遍历letters并StringBuffer.append(letters.charAt(i));dfs;再删去本次sub中index处的元素

注意Map中这样直接添加元素:

Map<Character, String> phoneMap = new HashMap<Character, String>() {{
            put('2', "abc");
            put('3', "def");
            put('4', "ghi");
            put('5', "jkl");
            put('6', "mno");
            put('7', "pqrs");
            put('8', "tuv");
            put('9', "wxyz");
        }};

可通过完整代码如下:

public List<String> letterCombinations(String digits) {
        List<String> ans = new ArrayList<>();
        if (digits.length() == 0) return ans;
        Map<Character, String> phoneMap = new HashMap<Character, String>() {{
            put('2', "abc");
            put('3', "def");
            put('4', "ghi");
            put('5', "jkl");
            put('6', "mno");
            put('7', "pqrs");
            put('8', "tuv");
            put('9', "wxyz");
        }};
        dfs(ans, phoneMap, digits, 0, new StringBuffer());
        return ans;
    }

    private void dfs(List<String> ans, Map<Character, String> phoneMap, String digits, int index, StringBuffer sub) {
        if (index == digits.length()) {   // 添加一个变为1,添加n个变为n
            ans.add(sub.toString());
        } else {
            char digit = digits.charAt(index);
            String letters = phoneMap.get(digit);   // 例:得到abc
            for (int i = 0; i < letters.length(); i++) {   // 遍历添加a、b、c
                sub.append(letters.charAt(i));
                dfs(ans, phoneMap, digits, index + 1, sub);   // 如果是数字2,就分三个分支回溯
                sub.deleteCharAt(index);   // 删除sub添加的第一个元素(abc中的一个)
            }
        }
    }

LeetCode 347前k个高频元素(数组中)

题目描述:
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:
输入: nums = [1], k = 1
输出: [1]

一、首先遍历整个数组,并使用哈希表记录每个数字出现的次数,并形成一个「出现次数数组」—[num, count]。使用优先级队列维护一个size=k的小顶堆【当size==k的时候,判断本次count>queue.peek()[1],弹出栈顶,add(new int[]{num, count})】。最后将queue输出为数组ret return

遍历Map使用

for (Map.Entry<Integer, Integer> entry : map.entrySet()) {   // 遍历k个不同的数字
            int num = entry.getKey(), count = entry.getValue();
        }

优先级队列默认从小到大排列(默认小顶堆,但是我们装进去的是数组,所以这里需要修改一下比较器)

直接修改()中的new Comparator idea就会弹出提示

PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {   // 优先级队列中元素为一个2元素数组[num, count]
    @Override
    public int compare(int[] o1, int[] o2) {
        return o1[1] - o2[1];   // 优先级队列中数组元素,按照索引[1](次数)从小到大
    }
});

可通过完整代码如下:

public int[] topKFrequent(int[] nums, int k) {
    Map<Integer, Integer> map = new HashMap<>();
    for (int n : nums) {   // 记录数组中各个int出现的次数
        map.put(n, map.getOrDefault(n, 0) + 1);
    }
    PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {   // 优先级队列中元素为一个2元素数组[num, count]
        @Override
        public int compare(int[] o1, int[] o2) {
            return o1[1] - o2[1];   // 优先级队列中数组元素,按照索引[1](次数)从小到大
        }
    });
    for (Map.Entry<Integer, Integer> entry : map.entrySet()) {   // 遍历k个不同的数字
        int num = entry.getKey(), count = entry.getValue();
        if (queue.size() == k) {
            if (count > queue.peek()[1]) {
                queue.poll();
                queue.add(new int[]{num, count});
            }
        } else {
            queue.offer(new int[]{num, count});
        }
    }
    // 返回数组
    int[] ret = new int[k];
    for (int i = 0; i < k; i++) {
        ret[i] = queue.poll()[0];   // 弹出栈顶元素
    }
    return ret;
}

LeetCode 34在排序数组中查找元素的第一个和最后一个位置

题目描述:
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:
输入:nums = [], target = 0
输出:[-1,-1]

一、要求时间复杂度为O(log n) ,这里采用二分法,我们需要找到第一个>=target的元素下标第一个>target的元素下标-1(修改binarySearch)

原始的二分法分为>、<、=,修改的只有if-else

二、初始ans=nums.length,这样子如果只有一个元素[1]的数组返回endIndex=1而不是0,-1后就为0而不是-1满足返回条件。while循环中判断>target或者>=target,else 就left=mid+1逼近边界

可通过完整代码

public int[] searchRange(int[] nums, int target) {
        int startIndex = binarySearch(nums, target, true);
        int endIndex = binarySearch(nums, target, false) - 1;
        if (startIndex <= endIndex && endIndex < nums.length && nums[startIndex] == target && nums[endIndex] == target) {
            return new int[]{startIndex, endIndex};
        }
        return new int[]{-1, -1};
    }

    private int binarySearch(int[] nums, int target, boolean flag) {
        int left = 0, right = nums.length - 1, ans = nums.length;   // 此处初始ans=nums.length应对数组只有一个元素[1],endIndex则会直接返回ans-1=0,而不是0-1=-1
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] > target || (flag && nums[mid] >= target)) {   // 当为true的时候,如果nums[mid]==target,继续right=mid-1、保存此次的ans、right左移动,直到找到startIndex
                right = mid - 1;   // 当为false的时候,只有nums[mid]>target才会移动right,保存ans;最后会保存第一个>target的ans
                ans = mid;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }

在这里插入图片描述

LeetCode 39组合总和

题目描述:
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:
输入: candidates = [2], target = 1
输出: []

一、DFS:我们定义递归函数dfs(),给一个参数index=0,每次选择跳过dfs(candidates, target, ans, sub, index + 1);或者使用当前index下标的数sub.add(candidates[index]); dfs(candidates, target - candidates[index], ans, sub, index); sub.remove(sub.size() - 1);【注意到每个数字可以无限被重复选取,所以dfs中为index】【因为只有一个sub,所以添加完返回上一个栈后进入跳过的dfs分支时,还要删掉sub列表的最后一个元素sub.remove(sub.size()-1)

二、终止条件,当index加到了candidates.length的时候直接return;当target减少到0的时候,List添加List:ans.add(new ArrayList<>(sub))

在这里插入图片描述

可通过完整代码

public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> sub = new ArrayList<>();
        dfs(candidates, target, ans, sub, 0);
        return ans;
    }

    private void dfs(int[] candidates, int target, List<List<Integer>> ans, List<Integer> sub, int index) {
        if (index == candidates.length) return;   // 递归终止条件1:candidates数组被全部用完
        if (target == 0) {   // 递归条件2:target==0
            ans.add(new ArrayList<>(sub));
            return;
        }
        dfs(candidates, target, ans, sub, index + 1);   // 跳过不用index下标的数
        if (target - candidates[index] >= 0) {
            sub.add(candidates[index]);
            dfs(candidates, target - candidates[index], ans, sub, index);   // 使用当前index下标的数
            sub.remove(sub.size() - 1);   // 因为sub只有一个在前面定义的全局变量,所以每次递归target-candidates[index]后,删掉前两行添加的sub.add(candidates[index])
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值