leetcode.842:将数组拆分成斐波那契序列-每日一题
给定一个数字字符串 S,比如 S = “123456579”,我们可以将它分成斐波那契式的序列 [123, 456, 579]。
形式上,斐波那契式序列是一个非负整数列表 F,且满足:
0 <= F[i] <= 2^31 - 1,(也就是说,每个整数都符合 32 位有符号整数类型);
F.length >= 3;
对于所有的0 <= i < F.length - 2,都有 F[i] + F[i+1] = F[i+2] 成立。
另外,请注意,将字符串拆分成小块时,每个块的数字一定不要以零开头,除非这个块是数字 0 本身。
返回从 S 拆分出来的任意一组斐波那契式的序列块,如果不能拆分则返回 []。
示例 1:
输入:“123456579”
输出:[123,456,579]
示例 2:
输入: “11235813”
输出: [1,1,2,3,5,8,13]
class Solution {
public List<Integer> splitIntoFibonacci(String S) {
/*基本思路:回溯算法,看题解比较清晰,这是我做的第一个回溯的题,还是得看题解才能做出来
https://leetcode-cn.com/problems/split-array-into-fibonacci-sequence/solution/javahui-su-suan-fa-tu-wen-xiang-jie-ji-b-vg5z/
*/
List<Integer> result = new ArrayList<Integer>();
backtrack(S.toCharArray(), result, 0);
return result;
}
private boolean backtrack(char[] digit, List<Integer> result, int index) {
//边界条件判断,如果截取完了,并且res长度大于等于3,表示找到了一个组合。
if(index == digit.length && result.size() >= 3) return true;
for(int i = index; i < digit.length; i++) {
//两位以上的数字不能以0开头
if(digit[index] == '0' && i > index) break;
//截取字符串转化为数字
long num = subDigit(digit, index, i + 1);
//如果截取的数字大于int的最大值,则终止截取
if (num > Integer.MAX_VALUE) break;
int size = result.size();
//如果截取的数字大于res中前两个数字的和,说明这次截取的太大,直接终止,因为后面越截取越大
if (size >= 2 && num > result.get(size - 1) + result.get(size - 2)) break;
if (size <= 1 || num == result.get(size - 1) + result.get(size - 2)) {
//把数字num添加到集合res中
result.add((int)num);
//如果找到了就直接返回
if(backtrack(digit, result, i + 1)) return true;
//如果没找到,就会走回溯这一步,然后把上一步添加到集合res中的数字给移除掉
result.remove( result.size() - 1);
}
}
return false;
}
//截取字符串S中的子串然后转换为十进制数字
private long subDigit(char[] digit, int start, int end) {
long result = 0;
for(int i = start; i < end; i++) {
result = result * 10 + digit[i] - '0';
}
return result;
}
}
leetcode.102:二叉树的层序遍历-hot100
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
/*基本思路:使用一个队列来存放节点,并指明每一层节点的个数
*/
public List<List<Integer>> levelOrder(TreeNode root) {
//存放节点
Queue<TreeNode> q = new LinkedList<>();
List<List<Integer>> result = new ArrayList<>();
if(root == null) return result;
q.offer(root);
while(true) {
//当队列为空说明遍历结束
if(q.size() == 0) break;
//当前这一层有多少个节点,用这个数来遍历就不会超出这一层的范围
int length = q.size();
List<Integer> temp = new ArrayList<>();
for(int i = 0; i < length; i++) {
//取队列头部
TreeNode node = q.poll();
temp.add(node.val);
//把左右节点放进队列末尾
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
}
result.add(temp);
}
return result;
}
}
leetocde.114:二叉树展开为链表-hot100
给定一个二叉树,原地将它展开为一个单链表。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public void flatten(TreeNode root) {
/*基本思路:类似中序遍历:把左子树、右子树捋顺,并拿到左子树最后一个节点,这最后一个节点的右孩子就接根节点的右边
再把根的左孩子置空
*/
if(root == null) return;
tree(root);
}
private TreeNode tree(TreeNode root) {
//节点为空直接返回
if(root == null) return null;
//左空右不空:展开右边,返回右边的最后一个节点
if(root.left == null && root.right != null) {
return tree(root.right);
}
//左不空右空:展开左子树,把左子树直接接到右子树
if(root.right == null && root.left != null) {
root.right = root.left;
root.left = null;
return tree(root.right);
}
//两子树都不空
if(root.left != null && root.right != null) {
//展开两子树
TreeNode left = tree(root.left);
TreeNode right = tree(root.right);
//把原来的右子树接到左子树的最后一个节点上去,再把左子树置空
TreeNode originRight = root.right;
root.right = root.left;
root.left = null;
left.right = originRight;
return right;
}
//没有子树,就返回自身
return root;
}
}
后序遍历
class Solution {
public void flatten(TreeNode root) {
if(root == null) return;
flatten(root.left);
flatten(root.right);
TreeNode tmp = root.right;
root.right = root.left;
root.left = null;
while(root.right != null) root = root.right;
root.right = tmp;
}
}
2020/12/10今天的每日一题手误点到两次,提交了两次错误。。。打扰了
leetocde.96:不同的二叉搜索树-hot100
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
class Solution {
public int numTrees(int n) {
/*基本思路:标签:动态规划(来自题解:画解算法)二叉搜索树确实不会啊啊啊啊
假设 n 个节点存在二叉排序树的个数是 G (n),令 f(i) 为以 i 为根的二叉搜索树的个数,则
G(n) = f(1) + f(2) + f(3) + f(4) + ... + f(n)G(n)=f(1)+f(2)+f(3)+f(4)+...+f(n)
当 i 为根节点时,其左子树节点个数为 i-1 个,右子树节点为 n-i,则
f(i) = G(i-1)*G(n-i)f(i)=G(i−1)∗G(n−i)
综合两个公式可以得到 卡特兰数 公式
G(n) = G(0)*G(n-1)+G(1)*(n-2)+...+G(n-1)*G(0)G(n)=G(0)∗G(n−1)+G(1)∗(n−2)+...+G(n−1)∗G(0)
*/
int[] dp = new int [n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i < n + 1; i++) {
for(int j = 0; j < i; j++) {
dp[i] += dp[j] * dp[i - j - 1];
}
}
return dp[n];
}
}
leetcode.649:Dota2 参议院-每日一题
Dota2 的世界里有两个阵营:Radiant(天辉)和 Dire(夜魇)
Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定。他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的一项:
禁止一名参议员的权利:
参议员可以让另一位参议员在这一轮和随后的几轮中丧失所有的权利。
宣布胜利:
如果参议员发现有权利投票的参议员都是同一个阵营的,他可以宣布胜利并决定在游戏中的有关变化。
给定一个字符串代表每个参议员的阵营。字母 “R” 和 “D” 分别代表了 Radiant(天辉)和 Dire(夜魇)。然后,如果有 n 个参议员,给定字符串的大小将是 n。
以轮为基础的过程从给定顺序的第一个参议员开始到最后一个参议员结束。这一过程将持续到投票结束。所有失去权利的参议员将在过程中被跳过。
假设每一位参议员都足够聪明,会为自己的政党做出最好的策略,你需要预测哪一方最终会宣布胜利并在 Dota2 游戏中决定改变。输出应该是 Radiant 或 Dire。
示例 1:
输入:“RD”
输出:“Radiant”
解释:第一个参议员来自 Radiant 阵营并且他可以使用第一项权利让第二个参议员失去权力,因此第二个参议员将被跳过因为他没有任何权利。然后在第二轮的时候,第一个参议员可以宣布胜利,因为他是唯一一个有投票权的人
class Solution {
public String predictPartyVictory(String senate) {
/*基本思路:把两个阵营分成两个队列,分别记录每个阵营中成员下标
取队首进行比较,如果一方的下标比较小,说明可以使另一方的队首当场去世,直接把另一方队首弹出
然后把自己的下标加上字符串长度,放到队尾,相当于下一次循环
至于为什么要让队首去世,是因为不让它去世,它后面就有可能让我方的人去世
可以看下官方的题解!德玛西亚!
*/
Queue<Integer> radiant = new LinkedList<>();
Queue<Integer> dire = new LinkedList<>();
//扫描记录下标
for(int i = 0; i < senate.length(); i++) {
if(senate.charAt(i) == 'R') {
radiant.offer(i);
}
else {
dire.offer(i);
}
}
while(!radiant.isEmpty() && !dire.isEmpty()) {
//其中一方队首比较小,弹出另一个队首,自己队首也弹出再加n然后加到队尾
if(radiant.peek() < dire.peek()) {
radiant.offer(radiant.poll() + senate.length());
dire.poll();
}
else {
dire.offer(dire.poll() + senate.length());
radiant.poll();
}
}
if(radiant.isEmpty()) {
return "Dire";
}
else {
return "Radiant";
}
}
}
leetcode.217:存在重复元素-每日一题
给定一个整数数组,判断是否存在重复元素。
如果任意一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。
示例 1:
输入: [1,2,3,1]
输出: true
class Solution {
public boolean containsDuplicate(int[] nums) {
/*基本思路:使用stream虽然慢但是很优雅*/
return Arrays.stream(nums).distinct().count() != nums.length;
}
}
leetcode.27:移除元素-错误次数太多了
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
class Solution {
public int removeElement(int[] nums, int val) {
/*基本思路:想了那么多花里胡哨的左右指针,结果最简单的方法就可以了
一个记录当前有效位置的下标,一个一直往后移动过的下标,后者遇到有效值就赋给前者
*/
int i = 0;
for(int k = 0; k < nums.length; k++) {
if(nums[k] != val) {
nums[i++] = nums[k];
}
}
return i;
}
}
leetcode.49:字母异位词分组-每日一题
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]
说明:
所有输入均为小写字母。
不考虑答案输出的顺序。
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
/* 基本思路:用map记录字符出现的字数,题解提供的思路更好,直接把字符和出现的个数拼接在一起作为key */
Map<String, List<String>> record = new HashMap<>();
for(String str : strs) {
//记录当前字符串中字符出现的次数
int[] nums = new int[26];
int length = str.length();
for(int i = 0; i < length; i++) {
nums[str.charAt(i) - 'a']++;
}
//把字符+字符出现次数 全部拼起来作为key
StringBuilder key = new StringBuilder();
for(int i = 0 ; i < 26; i++) {
key.append(i + 'a');
key.append(nums[i]);
}
//如果这个key所对应的value不存在就创建List并把它传进去
if(record.get(key.toString()) == null) {
List<String> strList = new ArrayList<>();
strList.add(str);
record.put(key.toString(), strList);
}
//已存在就直接加到该list的末尾
else {
List<String> strList = record.get(key.toString());
strList.add(str);
}
}
return new ArrayList<List<String>>(record.values());
}
}
好菜的耗时和内存。。
题解中还有一种非常emmm的写法!牛逼!速度还挺快
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
return new ArrayList<>(Arrays.stream(strs)
.collect(Collectors.groupingBy(str -> {
// 返回 str 排序后的结果。
// 按排序后的结果来grouping by,算子类似于 sql 里的 group by。
char[] array = str.toCharArray();
Arrays.sort(array);
return new String(array);
})).values());
}
}
leetcode.46:全排列-hot100
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
class Solution {
/*基本思路:来自labuladong的思路,回溯算法第二次练习
非常推荐他的算法小抄,希望大家都去看看
*/
private List<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
LinkedList<Integer> track = new LinkedList<>();
trackBack(nums, track);
return result;
}
private void trackBack(int[] nums, LinkedList<Integer> track) {
if(track.size() == nums.length) {
result.add(new LinkedList(track));
return;
}
for(int num : nums) {
if(track.contains(num)) {
continue;
}
track.add(num);
trackBack(nums, track);
track.removeLast();
}
}
}
leetocde.738:单调递增的数字-每日一题
给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。
(当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。)
示例 1:
输入: N = 10
输出: 9
示例 2:
输入: N = 1234
输出: 1234
class Solution {
public int monotoneIncreasingDigits(int N) {
/*基本思路:如果本身是递增的就直接返回。如果不是,则从末尾往前遍历
遇到不符合递增的就把那一位减一,后面全部变为9*/
char[] numStr = Integer.toString(N).toCharArray();
int i = 1;
//检查本身是否递增
while(i < numStr.length && numStr[i - 1] <= numStr[i]) {
i++;
}
//如果不是就开始遍历
if(i < numStr.length) {
//如果不符合递增,把这一位减1.并且这一位由于大于后一位所以不可能是0
while(i > 0 && numStr[i - 1] > numStr[i]) {
numStr[i - 1]--;
i--;
}
//把减1的那位后面的所有数字变成9
for(i = i + 1; i < numStr.length; i++) {
numStr[i] = '9';
}
}
return Integer.parseInt(new String(numStr));
}
}
leetcode.39:组合总和-hot100
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
class Solution {
/*基本思路:回溯算法模板,但是直接套用会出现重复的问题.
按照题解大佬的想法,遍历到后面的数字的时候就限定不能用前面的数字,可以达到去重的效果
*/
private List<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
LinkedList<Integer> track = new LinkedList<>();
backTrack(candidates, target, 0, track);
return result;
}
//begin为当前candidates的起始,往前的数字不能被选取
private void backTrack(int[] candidates, int target, int begin, LinkedList<Integer> track) {
//检验和是否已经满足或是大于
int sum = 0;
for(int num : track) {
sum += num;
}
if(sum > target) {
return;
}
if(sum == target) {
result.add(new LinkedList(track));
return;
}
//如果依然小于目标,则继续回溯
for(int i = begin; i < candidates.length; i++) {
//做出选择
track.offer(candidates[i]);
//进入下一层
backTrack(candidates, target, i, track);
//撤销选择
track.removeLast();
}
}
}
leetcode.17:电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
输入:“23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].
class Solution {
/*回溯算法!与上一道题类似,不多赘述
*/
List<String> result = new ArrayList<>();
//字符串的长度
int length = 0;
public List<String> letterCombinations(String digits) {
if(digits.length() == 0) return result;
//字符串的长度
length = digits.length();
StringBuilder track = new StringBuilder();
backtrack(digits, 0, digits.charAt(0) - '0', track);
return result;
}
//index为遍历字符串的下标 num为对应位置的数字
private void backtrack(String digits, int index, int num, StringBuilder track) {
//终止条件
if(track.length() == length) {
result.add(track.toString());
return;
}
//由于2到6和8是3个字母 7 9是4个字母,因此用条件区分,有点臭
if(num < 7) {
for(int i = 0; i < 3; i++) {
//做出选择
track.append((char)('a' + (num - 2) * 3 + i));
//如果还没到达末尾就继续
if(index + 1 < length) {
backtrack(digits, index + 1, digits.charAt(index + 1) - '0', track);
}
//-1是为了防止charAt超出范围的错误
else {
backtrack(digits, -1 , -1, track);
}
track.deleteCharAt(track.length() - 1);
}
}
else if(num == 7) {
for(int i = 0; i < 4; i++) {
track.append((char)('a' + (num - 2) * 3 + i));
if(index + 1 < length) {
backtrack(digits, index + 1, digits.charAt(index + 1) - '0', track);
}
else {
backtrack(digits, -1 , -1, track);
}
track.deleteCharAt(track.length() - 1);
}
}
else if(num == 8) {
for(int i = 0; i < 3; i++) {
track.append((char)('a' + (num - 2) * 3 + 1 + i));
if(index + 1 < length) {
backtrack(digits, index + 1, digits.charAt(index + 1) - '0', track);
}
else {
backtrack(digits, -1 , -1, track);
}
track.deleteCharAt(track.length() - 1);
}
}
else if(num == 9) {
for(int i = 0; i < 4; i++) {
track.append((char)('a' + (num - 2) * 3 + 1 + i));
if(index + 1 < length) {
backtrack(digits, index + 1, digits.charAt(index + 1) - '0', track);
}
else {
backtrack(digits, -1 , -1, track);
}
track.deleteCharAt(track.length() - 1);
}
}
}
}
leetcode.22:括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3
输出:[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
class Solution {
/*基本思路:今日份回溯算法杀疯了!直接看注释
*/
//记录结果
private List<String> result = new LinkedList<>();
public List<String> generateParenthesis(int n) {
StringBuilder track = new StringBuilder();
backtrack(n, track, 0, 0);
return result;
}
private void backtrack(int n, StringBuilder track, int left, int right) {
//长度足够时直接返回
if(track.length() == 2 * n) {
result.add(track.toString());
return;
}
//左括号可以添加的情况:左括号个数少于右括号
if(left < n) {
track.append('(');
backtrack(n, track, left + 1, right);
track.deleteCharAt(track.length() - 1);
}
//右括号可以添加的情况:右括号个数少于左括号
if(left > right) {
track.append(')');
backtrack(n, track, left, right + 1);
track.deleteCharAt(track.length() - 1);
}
}
}
leetocde.51:N 皇后
class Solution {
/*基本思路:回溯算法,主要是isValid函数一开始没想出来
*/
private List<List<String>> result = new LinkedList<>();
public List<List<String>> solveNQueens(int n) {
//初始化
StringBuilder col = new StringBuilder();
for(int i = 0; i < n; i++) {
col.append('.');
}
List<String> track = new ArrayList<>(n);
for(int i = 0; i < n; i++) {
track.add(col.toString());
}
backtracking(n, track, 0);
return result;
}
private void backtracking(int n, List<String> track, int row) {
//满足时添加,返回
if(row == n) {
result.add(new ArrayList(track));
return;
}
for(int i = 0; i < n; i++) {
//不合法直接跳过
if(!isValid(track, row, i)) {
continue;
}
//做出选择
StringBuilder temp = new StringBuilder(track.get(row));
temp.setCharAt(i, 'Q');
track.set(row, temp.toString());
//进入下一阶段
backtracking(n, track, row + 1);
//撤销
temp.setCharAt(i, '.');
track.set(row, temp.toString());
}
}
//检查是否合法
private boolean isValid(List<String> track, int row, int col) {
int n = track.size();
//检查当前列
for(int i = 0; i < n; i++){
if(track.get(i).charAt(col) == 'Q') {
return false;
}
}
//检查右下
for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if(track.get(i).charAt(j) == 'Q') {
return false;
}
}
//检查左上
for(int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if(track.get(i).charAt(j) == 'Q') {
return false;
}
}
return true;
}
}
leetocde.40:组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合
class Solution {
/*基本思路:回溯
一开始没有对candidates进行排序,去重的话比较难,排序之后只要比较前后两个的值是否一致就可以去重
*/
List<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
LinkedList<Integer> track = new LinkedList<>();
//升序
Arrays.sort(candidates);
backtracking(candidates, target, 0, track);
return result;
}
private void backtracking(int[] candidates, int target, int start, LinkedList<Integer> track) {
//如果等于就加入结果集
int sum = 0;
for(int num : track) {
sum += num;
}
if(sum == target) {
result.add(new LinkedList(track));
return;
}
else if(sum > target) {
return;
}
for(int i = start; i < candidates.length; i++) {
//因为candidates是升序的,一个大于target后面就全都大于target
if(sum + candidates[i] > target) {
break;
}
//如果前后两个数一致直接跳过
if(i > start && candidates[i] == candidates[i - 1]) {
continue;
}
//做出选择
track.offer(candidates[i]);
//进入下一层
backtracking(candidates, target, i + 1, track);
//撤销选择
track.removeLast();
}
}
}
leetocde.47:全排列 II
class Solution {
/*基本思路:回溯算法 但是最基本的回溯算法会造成大量的重复
因此需要先把原数组进行排序,然后每次做出选择时,都判断一下这个选择是否在之前做过
具体做法是维护一个布尔数组,如果要填入的这个数与前一个数相同,并且前一个数没有填入
说明这个数不再需要被填入了
*/
private List<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
LinkedList<Integer> track = new LinkedList<>();
//原数组排序,保证相同的数相连
Arrays.sort(nums);
//维护一个boolean数组,来显示对应下标的数是否已被填入
boolean[] record = new boolean[nums.length];
Arrays.fill(record, false);
trackBack(nums, track, record);
return result;
}
private void trackBack(int[] nums, LinkedList<Integer> track, boolean[] record) {
//达到条件就添加到结果集
if(track.size() == nums.length) {
result.add(new LinkedList(track));
return;
}
for(int i = 0; i < nums.length; i++) {
//1、如果当前数已填入就跳过 2、如果当前数没有填入,但是与它相同的前一个数也没有填入,也跳过
if(record[i] || (i > 0 && nums[i] == nums[i - 1] && !record[i - 1])) {
continue;
}
track.add(nums[i]);
record[i] = true;
trackBack(nums, track, record);
track.removeLast();
record[i] = false;
}
}
}