文章目录
491. 递增子序列
给你一个整数数组 nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
示例 1:
输入:nums = [4,6,7,7]
输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]
示例 2:
输入:nums = [4,4,3,2,1]
输出:[[4,4]]
提示:
1 <= nums.length <= 15
-100 <= nums[i] <= 100
分析
去重
由题目给出的例子中可以看出不能改变数组元素的位置,所以不能给数组排序,由上一篇博客可以子集2的分析得知排序是为了方便在递归过程中去重,那么现在不能排序了,该怎么去重呢?
其实还有另一种方法:就是将used变为一个数组,里边存放着本层使用过的元素,在判断要不要使用当前元素时,先判断used数组中是否包含该元素,若包含则不使用该元素。若不包含,那么在回溯到当前元素时,就将当前元素保存到used中标记当前元素已使用过了。
如何保证递增呢?
同样在判断要不要使用当前元素时,可以将当前元素与path的最后一个元素比较,如果当前元素更小,那么就不使用它,因为若是用它就会使path为非递增序列,最后添加到结果数组中时就与题目意思相悖。
var findSubsequences = function(nums) {
const res = [], path = [];
const dfs = function(start) {
// 结果限制条件至少有两个元素
if (path.length >= 2)
res.push([...path]);
let used = [];
for (let i = start; i < nums.length; i++) {
// 使用过后非递增则跳过该元素
if (used.includes(nums[i]) || path[path.length - 1] > nums[i])continue;
path.push(nums[i]);
dfs(i + 1);
path.pop();
// 记录当前元素
used.push(nums[i]);
}
}
dfs(0);
return res;
};
17. 电话号码的字母组合
给定一个仅包含数字 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']
的一个数字。
思路:
可以先将每个数字代表的字符串放在一个数组里,然后使用递归取给出的数字字符串中数字的映射,for循环遍历该映射取得每个字符。
单层递归逻辑
分析本题可以看出每层遍历都是取不同的集合字母且要取完每个集合中的字母,所以递归的需要for循环取字母且每层循环都从下标0开始,不需要给递归传入循环开始位置。
for (let i = 0; i < n; i++) {
path.push(map[digits[len] - 2][i]);
dfs (path, digitslen, len + 1);
path.pop();
}
递归终止条件
因为是每层是从一个数字所映射的字符串中取一个字符,所以可以用一个变量记录当前递归层数,当该变量与传入的数字字符串长度相等时说明已经得到一个组合。
if (len === digitslen) {
res.push (path.join(""));
return;
}
完整代码
var letterCombinations = function(digits) {
// 记录映射关系
const map = ["abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"];
const res = [];
const dfs = function (path, digitslen, len) {
// 递归终止条件
if (len === digitslen) {
res.push (path.join(""));
return;
}
// 取得每个数字映射字符串长度
const n = map[digits[len] - 2].length;
for (let i = 0; i < n; i++) {
path.push(map[digits[len] - 2][i]);
dfs (path, digitslen, len + 1);
path.pop();
}
}
// 传入数字字符串不为空时才进入递归
if (digits !== "")
dfs([], digits.length, 0)
return res;
};
另一种写法
var letterCombinations = function(digits) {
const map = ["abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"];
const res = [];
const dfs = function (path, digitslen, len) {
if (len === digitslen) {
res.push (path.join(""));
return;
}
for (const c of map[digits[len] - 2]) {
path.push(c);
dfs (path, digitslen, len + 1);
path.pop();
}
}
if (digits !== "")
dfs([], digits.length, 0)
else if (digits.length === 1){
return map[digits].split("");
}
return res;
};
31. 分割回文串
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
提示:
1 <= s.length <= 16
s
仅由小写英文字母组成
思路:分析题目本题可分为两步
- 分割字符串
- 验证字符串是否是回文字符串
怎么分割呢? 利用递归不断地将分割向前推进(调整开始标记),利用每层的for循环不断调整子串的长度,每调整一次长度的同时就验证一次字符串是否合法,若当前截取的字符串是有效的那么就将其保存,递归进入下一层且将开始标记变为当前字符串结束位置下标加1。
使用双指针方法来检查字符串是否是回文字符串。
var partition = function(s) {
const res = [], path = [];
// 递归分割字符串
let dfs = function (start) {
// 递归终止条件
if (start >= s.length) {
res.push ([...path]);
return;
}
for (let i = start; i < s.length; i++) {
// 这里当前字符串不合法不能直接退出循环,因为你不能保证字符串调整后也不是回文串
if (!isTrue(start, i)) continue;
path.push(s.substring(start, i + 1));
dfs (i + 1);
path.pop();
}
}
// 验证是否是回文字符串
let isTrue = function(l, r) {
while (l < r) {
if (s[r] !== s[l]) {
return false;
}
l++;
r--;
}
return true;
}
dfs (0);
return res;
};
93. 复原 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 地址。
给定一个只包含数字的字符串 s
,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s
中插入 '.'
来形成。你 不能 重新排序或删除 s
中的任何数字。你可以按 任何 顺序返回答案。
示例 1:
输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]
示例 2:
输入:s = "0000"
输出:["0.0.0.0"]
示例 3:
输入:s = "101023"
输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
提示:
1 <= s.length <= 20
s
仅由数字组成
思路:分析题目可以得出要解此题需要做两步工作
- 分割字符串
- 验证字符串的合法性
利用递归不断地将分割向前推进(调整开始标记),利用每层的for循环不断调整子串的长度,每调整一次长度的同时就验证一次字符串是否合法,若当前截取的字符串是有效的那么就将其保存,递归进入下一层且将开始标记变为当前字符串结束位置下标加1。
验证合法性, 长度不为1且首字符为0的字符串则是含前导0的字符串,不合法,若字符串的整数值大于255也不合法。
递归终止条件,只要字符串分割完了就需要结束递归,只有path的长度为四才是合法IP,记录进结果数组中。
var restoreIpAddresses = function(s) {
const res = [], path = [];
// 递归分割字符串
let dfs = function (start) {
// 递归终止条件
if (start >= s.length ) {
// 若path长度为四那么就是有效IP
if (path.length === 4)
res.push (path.join('.'));
return;
}
for (let i = start; i < s.length; i++) {
// 截取字符串
const ipItem = s.substring(start, i + 1);
// 验证是否合法,如果不合法那么包含当前字符串的所有IP都不合法,直接退出循环(剪枝)
if (!isTrue(ipItem)) break;
path.push(ipItem);
dfs (i + 1);
path.pop();
}
}
// 验证子串是否合法
let isTrue = function(ipItem) {
if ((ipItem[0] === '0' && ipItem.length !== 1) || ipItem > 255) return false;
return true;
}
dfs (0);
return res;
};