LeetCode142--三数之和、电话号码的字母组合

本文详细介绍了如何使用双指针技术解决寻找数组中三数之和为0的问题,通过优化循环避免了重复计算,并探讨了电话号码的字母组合问题,提出了一种基于回溯算法的解决方案,同时提供了一种使用队列的巧妙方法。这些算法在处理数组和字符串组合问题时展现出高效性和灵活性。
摘要由CSDN通过智能技术生成

1、三数之和(L15)

//给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重
//复的三元组。 
//
// 注意:答案中不可以包含重复的三元组。 
//
// 
//
// 示例 1: 
//
// 
//输入:nums = [-1,0,1,2,-1,-4]
//输出:[[-1,-1,2],[-1,0,1]]
// 
//
// 示例 2: 
//
// 
//输入:nums = []
//输出:[]
// 
//
// 示例 3: 
//
// 
//输入:nums = [0]
//输出:[]
// 
//
// 
//
// 提示: 
//
// 
// 0 <= nums.length <= 3000 
// -105 <= nums[i] <= 105 
// 
// Related Topics 数组 双指针

三数之和其实最简单直观的方法就是直接采用for循环来做,但是这样一来时间复杂度高,二来后期需要花比较大的代价进行去重。代码如下:

public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> lists = new ArrayList<>();
        for (int i = 0; i < nums.length - 2; i++) {
            for (int j = i+1; j < nums.length - 1; j++) {
                int num = nums[i]+nums[j];
                for (int k = j+1; k < nums.length; k++) {
                    if(num == -1 * nums[k]){
                        ArrayList<Integer> list = new ArrayList<>();
                        list.add(nums[i]);
                        list.add(nums[j]);
                        list.add(nums[k]);
                        Collections.sort(list);
                        if(!lists.contains(list)){
                            lists.add(list);
                        }
                    }
                }
            }
        }
        return lists;
}

其次就是不单独采用集合进行降重,这样就需要我们对数进行排序,这样如果保证我们的三个数是a不大于b,b不大于c。但是这样仍然会存在比较严重的重复问题,比如:

[1,2,3,4,5,5,-6]

这一串数,如果我们取了a、b、c分别为1、5、-6,然后继续向后取值,b仍然会取5,然后就会出现重复,所以我们每一个元素在自己的循环里都需要避免重复。

即使这样做了之后,如下:

public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> lists = new ArrayList<>();
        for (int i = 0; i < nums.length - 2; i++) {
            if(i == 0 || nums[i] != nums[i-1]){
                for (int j = i+1; j < nums.length - 1; j++) {
                    if(j == i + 1 || nums[j] != nums[j-1]){
                        for (int k = j+1; k < nums.length; k++) {
                            if(k == j+1 || nums[k] != nums[k-1]){
                                if(nums[i] + nums[k] + nums[j] == 0){
                                    List<Integer> list = new ArrayList<>();
                                    list.add(nums[i]);
                                    list.add(nums[j]);
                                    list.add(nums[k]);
                                    lists.add(list);
                                }
                            }
                        }
                    }
                }
            }
        }
        return lists;
}

依然是超时,因为没有脱离三层循环的框架,但其实我们细琢磨,我们可以直接每一轮循环看成是两数相和,因为每次循环a都是固定的,所以我们就只需要管b和c就行了,而b和c之和要求是固定值,那么增加b,c必定要减小,所以我们可以看成是一个双指针的问题,每次固定a然后取b值,随后不断减小c值,直到b+c<=-a。这个时候我们的c值是可以记录下来的,下次b只会更大,所以c的指针只能从当前位置往前移,这样就避免了重复的循环的发生。关键是一些判别条件,具体看程序

public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> lists = new ArrayList<>();
        for (int i = 0; i < nums.length-2; i++) {
            if(i == 0 || nums[i] != nums[i-1]){
                int k = nums.length-1;
                for (int j = i+1; j < nums.length-1; j++) {
                    if(j == i+1 || nums[j] != nums[j - 1]){
                        while (j < k && nums[j] + nums[k] + nums[i] > 0) {
                            --k;
                        }
                        // 如果指针重合,随着 b 后续的增加
                        // 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出b的循环,可以改变a了
                        if (j == k) {
                            break;
                        }
                        if (nums[i] + nums[j] + nums[k] == 0) {
                            List<Integer> list = new ArrayList<Integer>();
                            list.add(nums[i]);
                            list.add(nums[j]);
                            list.add(nums[k]);
                            lists.add(list);
                        }
                    }
                }
            }
        }
        return lists;
    }

2、电话号码的字母组合(L17)

//给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 
//
// 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 
//
// 
//
// 
//
// 示例 1: 
//
// 
//输入:digits = "23"
//输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
// 
//
// 示例 2: 
//
// 
//输入:digits = ""
//输出:[]
// 
//
// 示例 3: 
//
// 
//输入:digits = "2"
//输出:["a","b","c"]
// 
//
// 
//
// 提示: 
//
// 
// 0 <= digits.length <= 4 
// digits[i] 是范围 ['2', '9'] 的一个数字。 
// 
// Related Topics 深度优先搜索 递归 字符串 回溯算法

这个就相当于是一个N叉树,然后找到每一根节点在叶子节点的路径,所以用回溯的方法

class Solution {
    private String[] letterMap = {" ",
            "",     //1
            "abc",  //2
            "def",  //3
            "ghi",  //4
            "jkl",  //5
            "mno",  //6
            "pqrs", //7
            "tuv",  //8
            "wxyz"  //9
    };
    private List<String> res;
    public List<String> letterCombinations(String digits) {
        res = new ArrayList<String>();
        if(digits.equals("")){
            return res;
        }
        helper(digits, 0, "");
        return res;
    }
    private void helper(String digits, int index, String s){
        //一旦形成的字母字符串的长度等于了数字字符串的长度就直接返回
        if(index == digits.length()){
            res.add(s);
            return;
        }
        char c = digits.charAt(index);
        String letters = letterMap[c - '0'];
        for (int i = 0; i < letters.length(); i++) {
            helper(digits, index + 1, s+letters.charAt(i));
        }
    }
}

 这个底下还有一个比较小巧牛逼的方法,就是利用队列来进行求解,这种方法十分巧妙,比如我们的数字数组是[2,3,4]

首先在队列中存[a,b,c],然后我们将a给移出去,然后在后面添加ad,ae,af,这个时候数组就变成了[b,c,ad,ae,af],然后就是这个逻辑,最后就能将一整个N叉树遍历完成。

public List<String> letterCombinations(String digits) {
        LinkedList<String> ans = new LinkedList<String>();
        if(digits.isEmpty()) return ans;
        String[] mapping = new String[] {"0", "1", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        ans.add("");
        for(int i =0; i<digits.length();i++){
            int x = Character.getNumericValue(digits.charAt(i));
            while(ans.peek().length()==i){
                String t = ans.remove();
                for(char s : mapping[x].toCharArray())
                    ans.add(t+s);
            }
        }
        return ans;
    }

 

好的,下面是电话号码字母组合问题的 C++ 代码实现: ``` class Solution { public: vector<string> letterCombinations(string digits) { unordered_map<char, string> mapping = { {'2', "abc"}, {'3', "def"}, {'4', "ghi"}, {'5', "jkl"}, {'6', "mno"}, {'7', "pqrs"}, {'8', "tuv"}, {'9', "wxyz"} }; vector<string> res; if (digits.empty()) { return res; } string combination; backtrack(res, combination, digits, 0, mapping); return res; } void backtrack(vector<string>& res, string& combination, string& digits, int index, unordered_map<char, string>& mapping) { if (index == digits.size()) { res.push_back(combination); return; } for (char c : mapping[digits[index]]) { combination.push_back(c); backtrack(res, combination, digits, index + 1, mapping); combination.pop_back(); } } }; ``` 其中 `letterCombinations` 函数用来生成所有的字母组合,`backtrack` 函数用来进行回溯操作。在 `letterCombinations` 函数中,首先根据数字字符和字母的映射关系创建了一个 `unordered_map` 对象 `mapping`。然后定义了一个空字符串 `combination` 和一个空 vector `res` 来保存最终结果。最后调用了 `backtrack` 函数来生成所有的字母组合。在 `backtrack` 函数中,首先判断是否达到了数字字符串的末尾,如果是,则将当前的 `combination` 字符串保存到 `res` 中。否则,遍历当前数字字符所能表示的所有字母,依次加入到 `combination` 字符串中,然后递归调用 `backtrack` 函数,添加下一个数字字符所能表示的字母。递归完成后,需要将 `combination` 字符串还原到上一个状态,以便进行下一次回溯。最终返回 `res` 数组即可。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值