【leetcode-回溯】复原IP地址/子集/组合/电话号码的字母组合/二进制手表

复原IP地址

给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效 IP 地址。

示例 1:
输入:s = “25525511135”
输出:[“255.255.11.135”,“255.255.111.35”]

示例 2:
输入:s = “0000”
输出:[“0.0.0.0”]

示例 3:
输入:s = “1111”
输出:[“1.1.1.1”]

示例 4:
输入:s = “010010”
输出:[“0.10.0.10”,“0.100.1.0”]

示例 5:
输入:s = “101023”
输出:[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“101.0.2.3”]

回溯法

class Solution {
    private char[] charList;
    private List<String> result = new ArrayList<String>();
    private int[] segments = new int[4];
    private int len = 0;

    public List<String> restoreIpAddresses(String s) {
        charList = s.toCharArray();
        len = charList.length;
        backtrack(0, 0);
        return result;
    }

    private void backtrack(int start, int n) {
        if (n == 4) {
            if (start == len) {
                StringBuilder sb = new StringBuilder();
                sb.append(segments[0]);
                for (int i = 1; i < 4; i++) 
                    sb.append(".").append(segments[i]);
                result.add(sb.toString());
            }
            return;
        }
        if (start == len)
            return;

        if (charList[start] == '0') {
            segments[n] = 0;
            backtrack(start + 1, n + 1);
        } else {
            int num = 0;
            for (int i = start; i < len; i++) {
                num = 10 * num + (charList[i] - '0');
                if (num > 0 && num < 256) {
                    segments[n] = num;
                    backtrack(i + 1, n + 1);
                } else {
                    break;
                }
            }
        }
    }
}

子集1

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

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

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

迭代法

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> results = new ArrayList<>();
        results.add(new ArrayList<Integer>());
        for (int n : nums) {
            int size = results.size();
            for (int i = 0; i < size; i++) {
                List<Integer> cur = new ArrayList<>(results.get(i));
                cur.add(n);
                results.add(cur);
            }
        }
        return results;
    }
}

回溯法

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> results = new ArrayList<>();
        backtrack(results, new ArrayList<Integer>(), 0, nums);
        return results;
    }

    private void backtrack(List<List<Integer>> results, List<Integer> subset, int start, int[] nums) {
        results.add(new ArrayList<>(subset));
        for (int i = start; i < nums.length; i++) {
            subset.add(nums[i]);
            backtrack(results, subset, i + 1, nums);
            subset.remove(subset.size() - 1);
        }
    }
}

子集2

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。

示例:
输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

回溯法

class Solution {
    private int len;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        this.len = nums.length;
        Arrays.sort(nums);

        List<List<Integer>> result = new ArrayList<>();
        backtrack(nums, result, new ArrayList<>(), 0);
        return result;
    }

    private void backtrack(int[] ns, List<List<Integer>> res, List<Integer> comb, int start) {
        res.add(new ArrayList<>(comb));

        for (int i = start; i < len; i++) {
            if (i > start && ns[i] == ns[i - 1])
                continue;
            comb.add(ns[i]);
            backtrack(ns, res, comb, i + 1);
            comb.remove(comb.size() - 1);
        }
    }
}

迭代法

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        result.add(new ArrayList<>());
        Arrays.sort(nums);

        int start = 1;
        for (int i = 0; i < nums.length; i++) {
            List<List<Integer>> cur_result = new ArrayList<>();
            for (int j = 0; j < result.size(); j++) {
                if (j < start && i > 0 && nums[i] == nums[i - 1])
                    continue;
                List<Integer> combine = new ArrayList<>(result.get(j));
                combine.add(nums[i]);
                cur_result.add(combine);
            }
            start = result.size();
            result.addAll(cur_result);
        }
        return result;
    }
}

组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

回溯法

class Solution {
    private int count = 0;
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> results = new ArrayList<>();
        backtrack(results, new ArrayList<Integer>(), 1, n, k);
        return results;
    }

    private void backtrack(List<List<Integer>> results, List<Integer> subset, int start, int n, int k) {
        if (subset.size() + n - start + 1 < k)
            return;

        if (subset.size() == k) {
            results.add(new ArrayList<>(subset));
            return;
        }
        
        
        for (int i = start; i <= n; i++) {
            subset.add(i);
            backtrack(results, subset, i + 1, n, k);
            subset.remove(subset.size() - 1);
        }
    }
}

电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
在这里插入图片描述

示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]

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

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

回溯法

在这里插入图片描述

class Solution {
    public List<String> letterCombinations(String digits) {
        List<String> result = new ArrayList<>();
        if (digits.length() < 1) {
            return result;
        }

        String[] letterMap = { "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        backtrack(result, letterMap, digits, 0, new StringBuffer());
        return result;
    }

    private void backtrack(List<String> result, String[] letterMap, String digits, int index, StringBuffer curString) {
        if (index == digits.length()) {
            result.add(curString.toString());
        } else {
            char curLetter = digits.charAt(index);
            String letters = letterMap[curLetter - '2'];
            for (int i = 0; i < letters.length(); i++) {
                char letter = letters.charAt(i);
                curString.append(letter);
                backtrack(result, letterMap, digits, index+1, curString);
                curString.deleteCharAt(index);
            }
        }
    }
}

二进制手表

二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。每个 LED 代表一个 0 或 1,最低位在右侧。

例如,下面的二进制手表读取 “3:25” 。
在这里插入图片描述

给你一个整数 turnedOn ,表示当前亮着的 LED 的数量,返回二进制手表可以表示的所有可能时间。你可以 按任意顺序 返回答案。

小时不会以零开头:

  • 例如,“01:00” 是无效的时间,正确的写法应该是 “1:00” 。

分钟必须由两位数组成,可能会以零开头:

  • 例如,“10:2” 是无效的时间,正确的写法应该是 “10:02” 。

示例 1:
输入:turnedOn = 1
输出:[“0:01”,“0:02”,“0:04”,“0:08”,“0:16”,“0:32”,“1:00”,“2:00”,“4:00”,“8:00”]

示例 2:
输入:turnedOn = 9
输出:[]

回溯法

class Solution {
    private boolean[] used = new boolean[10];
    public List<String> readBinaryWatch(int turnedOn) {
        List<String> result = new ArrayList<>();
        if (turnedOn > 8 || turnedOn < 0)
            return result;

        backtrack(result, 0, 0, turnedOn);
        return result;
    }

    private void backtrack(List<String> result, int start, int cur, int num) {
        int minute = 0x3f & cur;
        int hour = 0xf & (cur >> 6);
        if (minute > 59 || hour > 11)
            return;
        if (num == 0) {
            StringBuilder sb = new StringBuilder();
            sb.append(hour).append(':');
            if (minute < 10) 
                sb.append('0');
            sb.append(minute);
            result.add(sb.toString());
            return;
        } 

        for (int i = start; i < 10; i++) {
            if (used[i])
                continue;
            used[i] = true;
            backtrack(result, i + 1, cur | (1 << i), num - 1);
            used[i] = false;
        }
    }
}

哈希表存储+组合

class Solution {
    public List<String> readBinaryWatch(int turnedOn) {
        List<String> result = new ArrayList<>();
        if (turnedOn > 8 || turnedOn < 0)
            return result;

        Map<Integer, List<Integer>> minuteMap = new HashMap<>();
        Map<Integer, List<Integer>> hourMap = new HashMap<>();
        for (int i = 0; i < 60; i++) {
            minuteMap.put(i, new ArrayList<Integer>());
            if (i < 12)
                hourMap.put(i, new ArrayList<Integer>());
        }
        for (int i = 0; i < 60; i++) {
            int count = Integer.bitCount(i);
            minuteMap.get(count).add(i);
            if (i < 12)
                hourMap.get(count).add(i);
        }

        for (int i = 0; i <= turnedOn; i++) {
            List<Integer> minutes = minuteMap.get(turnedOn - i), hours = hourMap.get(i);
            for (int h : hours) {
                for (int m : minutes) {
                    StringBuilder sb = new StringBuilder();
                    sb.append(h).append(':');
                    if (m < 10)
                        sb.append('0');
                    sb.append(m);
                    result.add(sb.toString());
                }
            }
        }
        return result;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我来用中文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的题目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问题,比如最长公共子序列、背包问题、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的题型,很多题目都需要用到动态规划的思想来解决。 这个链接里包含了很多关于动态规划的题目,按照难度从简单到困难排列。每个题目都有详细的题目描述、输入输出样例、题目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提高自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问题和最优子结构性质的问题。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问题,如背包问题、图像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统中,动态规划也广泛应用于各种优化算法中,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,题目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法。 LeetCode的动态规划标签下的题目涵盖了各种难度级别和场景的问题。从简单的斐波那契数列、迷宫问题到可以用于实际应用的背包问题、最长公共子序列等,难度不断递进且话题丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的题目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问题拆分成子问题的方式进行求解。在LeetCode中,动态规划标签涵盖了众多经典和优美的算法问题,例如斐波那契数列、矩阵链乘法、背包问题等。 动态规划的核心思想是“记忆化搜索”,即将中间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程中的中间值,例如动态规划求解斐波那契数列问题时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列中,第j个元素的值。 在LeetCode中,动态规划标签下有众多难度不同的问题。例如,经典的“爬楼梯”问题,要求我们计算到n级楼梯的方案数。这个问题的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问题还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问题、保存中间状态来求解问题。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问题,但在某些场景下并不适用。例如,某些问题的状态转移过程比较复杂,或者状态转移方程中存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解题都非常重要。除了刷题以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法与数据结构基础》等,来深入理解这种算法思想。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值