406-根据身高重建队列
…刚开始是乱序的—》》调整为有序
题目描述:整数对 (h, k) 表示,其中 h 是这个人的身高,k 是排在这个人前面且身高大于或等于 h 的人数。
渔(套路):一般这种数对,还涉及排序的,根据第一个元素正向排序,根据第二个元素反向排序,或者根据第一个元素反向排序,根据第二个元素正向排序,往往能够简化解题过程。
在本题目中,我首先对数对进行排序,按照数对的元素 1 降序排序,按照数对的元素 2 升序排序。原因是,按照元素 1
进行降序排序,对于每个元素,在其之前的元素的个数,就是大于等于他的元素的数量,而按照第二个元素正向排序,我们希望 k
大的尽量在后面,减少插入操作的次数。 作者:LeahChao
链接:https://leetcode-cn.com/problems/queue-reconstruction-by-height/solution/xian-pai-xu-zai-cha-dui-dong-hua-yan-shi-suan-fa-g/
class Solution {
public int[][] reconstructQueue(int[][] people) {
//先对数组进行排序,按照第一个元素进行降序,按照第二个元素升序
//这样的原因是:“按照第二个元素正向排序,我们希望 k 大的尽量在后面,减少插入操作的次数。
//”不止是为了减少插入次数,也是为了保证正确性。 举个例子,在身高一样,k不一样的时候,譬如[5,2]和[5,3],
//对于最后排完的数组,[5,2]必然在[5,3]的前面。所以如果遍历的时候[5,3]在前面,等它先插入完,
//这个时候它前面会有3个大于等于它的数组对,遍历到[5,2]的时候,它必然又会插入[5,3]前面
//(因为它会插入链表索引为2的地方),这个时候[5,3]前面就会有4个大于等于它的数组对了,这样就会出错。
Arrays.sort(people,new Comparator<int[]>(){
public int compare(int[] person1,int[] person2){
if(person1[0] != person2[0]){
return person2[0] - person1[0];
}else{
return person1[1]-person2[1];
}
}
});
//新建list,保存结果,都是插入操作,因此用链表
List<int[]> list = new LinkedList<>();
for(int i=0; i<people.length;i++){
if(list.size()>people[i][1]){
//结果集中元素个数大于第i个人前面应有的人数时,将第i个人插入到结果集的 people[i][1]位置
//由于是按照第一个元素降序排列的,因此插入到对应位置,前面就一定会有people[i][1]个人大于或者等于他
list.add(people[i][1],people[i]);
}else{
//结果集中元素个数小于等于第i个人前面应有的人数时,将第i个人追加到结果集的后面
list.add(list.size(),people[i]);
}
}
//list转为数组
return list.toArray(new int[list.size()][]);
}
}
416. 分割等和子集
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:输入:nums = [1,2,3,5] 输出:false 解释:数组不能分割成两个元素和相等的子集。
class Solution {
//问题可以转化为找数组内若干元素和等于总和的一半
/**
dp[i][j] 表示从数组[0,i]中选取若干个整数(可以是0个)组成一些数组,这些数字的和等于target
状态方程:j>=nums[i] ,则可以选取也可以不选取:
1.选取 dp[i][j] = dp[i-1][j-nums[i]];
2.不选取 dp[i][j] = dp[i-1][j];
j<nums[i],就肯定不能选取,则这个时候 dp[i][j] = dp[i-1][j]
边界:i==0时:只有dp[i][nums[i]]=true
对于dp[i][0] ,只有i在取值范围内都是true
*/
public boolean canPartition(int[] nums) {
int n = nums.length;
//n小于2肯定不行
if(n<2) return false;
int sum =0,maxNum = 0;
//求数组的和,并找出最大值
for(int num:nums){
sum += num;
maxNum = Math.max(maxNum,num);
}
int target = sum/2;
//sum是奇数不行(两个相同的数加起来一定是偶数)
//maxNum大于sum/2返回false
if(sum%2 !=0 || maxNum > target) return false;
boolean[][] dp = new boolean[n][target+1];
//边界
for(int i=0;i<n;i++){
dp[i][0] = true;
}
dp[0][nums[0]] = true;
for(int i=1;i<n;i++){
int num = nums[i];
for(int j=1;j<=target;j++){
if(j>=num){
dp[i][j] = dp[i-1][j]|dp[i-1][j-num];
}else{
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n-1][target];
}
}
437. 路径总和 III
给定一个二叉树,它的每个结点都存放着一个整数值。
找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。
class Solution {
/**
前缀和+dfs
在同一个路径之下(可以理解成二叉树从root节点出发,到叶子节点的某一条路径),如果两个数的前缀总和是相同的,那么这些节点之间的元素总和为零。进一步扩展相同的想法,如果前缀总和currSum,在节点A和节点B处相差target,则位于节点A和节点B之间的元素之和是target。
也就是不断遍历回溯查找从某点出发前缀和等于target的过程
*/
public int pathSum(TreeNode root, int targetSum) {
//key是前缀和,value 是大小为key的前缀和出现的次数
Map<Integer,Integer> prefixSumCount = new HashMap<>();
//前缀和为0的一条路径
prefixSumCount.put(0,1);
//前缀和的递归遍历
return recursionPathSum(root, prefixSumCount, targetSum, 0);
}
//前缀和的递归回溯思路
//从当前节点反推到根节点(反推比较好理解,正向其实也只有一条),有且仅有一条路径,因为这是一棵树
//如果此前有和为currSum-target,而当前的和又为currSum,两者的差就肯定为target了
//所以前缀和对于当前路径来说是唯一的,当前记录的前缀和,在回溯结束,回到本层时去除,保证其不影响其他分支的结果
private int recursionPathSum(TreeNode node, Map<Integer, Integer> prefixSumCount, int target, int currSum) {
// 1.递归终止条件
if(node == null) return 0;
//2.本层要做的事情
int res = 0;
//当前路径上的和
currSum += node.val;
//看看root到当前节点这条路上是否存在节点前缀和加target为currSum的路径
//当前节点->root节点反推,有且仅有一条路径,如果此前有和为currSum-target,而当前的和又为currSum,两者的差就肯定为target了
// currSum-target相当于找路径的起点,起点的sum+target=currSum,当前点到起点的距离就是target
res += prefixSumCount.getOrDefault(currSum - target, 0);
// 更新路径上当前节点前缀和的个数
prefixSumCount.put(currSum, prefixSumCount.getOrDefault(currSum, 0) + 1);
//---核心代码
// 3.进入下一层
res += recursionPathSum(node.left, prefixSumCount, target, currSum);
res += recursionPathSum(node.right, prefixSumCount, target, currSum);
// 4.回到本层,恢复状态,去除当前节点的前缀和数量
prefixSumCount.put(currSum, prefixSumCount.get(currSum) - 1);
return res;
}
}
438. 找到字符串中所有字母异位词
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:
字母异位词指字母相同,但排列不同的字符串。 不考虑答案输出的顺序。 示例 1:
输入: s: “cbaebabacd” p: “abc”
输出: [0, 6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的字母异位词。
起始索引等于 6 的子串是 “bac”, 它是
“abc” 的字母异位词。
示例 2:输入: s: “abab” p: “ab”
输出: [0, 1, 2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的字母异位词。
起始索引等于 1 的子串是 “ba”, 它是 "ab"的字母异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的字母异位词。
class Solution {
//滑动窗口
public List<Integer> findAnagrams(String s, String p) {
int[] fre = new int[26];
//表示窗口内相差的字符的数量
int dif=0;
//统计匹配串中字符出现的频数
for(char c:p.toCharArray()){
fre[c-'a']++;
dif++;
}
//left用于标记正在匹配的滑动窗口的最左端
//right用来标记s中,当前正在匹配的字符
int left=0,right=0;
int len = s.length();
char[] ch = s.toCharArray();
List<Integer> list = new ArrayList<>();
while(right<len){
char rightChar = ch[right];
//当前字符是p中的字符
if(fre[rightChar-'a']>0){
fre[rightChar-'a']--;
//差距减少
dif--;
//香右移动继续匹配
right++;
//差距为0是,说明当前窗口内为所求
if(dif==0) list.add(left);
}else{
//rightChar 是p以外的字符,如'c'此时 left 和right都应该后移,同时恢复fre数组,
//因为left在最左边,因此通过fre[array[left]-'a']++;就可以恢复
while(fre[rightChar-'a']<=0&&left<right){
fre[ch[left]-'a']++;
left++;
dif++;
}
//移动到left==right时
if(left==right){
//如果此时字符在p中
if(fre[ch[right]-'a']>0){
continue;
}else{
left++;
right++;
}
}
}
}
return list;
}
}
448. 找到所有数组中消失的数字
给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。
示例 1:
输入:nums = [4,3,2,7,8,2,3,1] 输出:[5,6]
示例 2:输入:nums = [1,1] 输出:[2]
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> ans = new ArrayList<>();
int n=nums.length;
if(nums==null || n==0) return ans;
for(int num:nums){
//num-1是因为下标是从0~n-1,取余防止越界
int x = (num-1)%n;
//这样以缺失元素为下标的位置上的数就会小于n
nums[x] +=n;
}
for(int i=0;i<n;i++){
if(nums[i]<=n){
//返回小于等于n位置上的下标
ans.add(i+1);
}
}
return ans;
}
}