根据身高重建队列
假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列,使得队列满足上面的性质。
注意:
总人数少于1100人。
示例
输入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
输出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
贪心算法
1)让我们从最简单的情况下思考,当队列中所有人的 (h,k) 都是相同的高度 h,只有 k 不同时,解决方案很简单:每个人在重建后队列的位置(索引)即 k。
2)即使不是所有人都是同一高度,这个策略也是可行的。因为个子矮的人相对于个子高的人是 “看不见” 的,所以可以先安排个子高的人。
3)总结下来的算法就是:先按照身高从大到小排序,然后依序遍历,用LinkedList链表存储重建后的序列,每个人插入重建后序列时,位置(索引)即为其对应的k值。
public static int[][] reconstructQueue(int[][] people) {
Arrays.sort(people, new Comparator() {
@Override
public int compare(int[] o1, int[] o2) {
// 按照身高排序, 身高一样按照k排序(为了减少链表的中间插入操作)
return o2[0] == o1[0] ? o1[1] - o2[1] : o2[0] - o1[0];
}
});
LinkedList sorted = new LinkedList<>();
for(int[] p : people){
sorted.add(p[1], p);
}
int[][] ret = new int[people.length][2];
int ind = 0;
for(int[] p : sorted){
ret[ind++] = p;
}
return ret;
}
最长上升子序列(https://leetcode-cn.com/problems/longest-increasing-subsequence/)
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
动态规划
定义 dp[i]为考虑前 i 个元素,以第 i个数字结尾的最长上升子序列的长度,注意nums[i] 必须被选取。
我们从小到大计算 dp[]数组的值,在计算 dp[i]之前,我们已经计算出dp[0…i−1] 的值,则状态转移方程为:
即考虑往 dp[0…i−1] 中最长的上升子序列后面再加一个 nums[i]。由于 dp[j] 代表 nums[0…j] nums[j] 结尾的最长上升子序列,所以如果能从 dp[j]这个状态转移过来,那么 nums[i] 必然要大于nums[j],才能将 nums[i] 放在 nums[j] 后面以形成更长的上升子序列。
最后,整个数组的最长上升子序列即所有 dp[i] 中的最大值。
总结:最后的求解值,未必一定是dp数组中的某个值,而是通过简单的遍历可以从dp数组中获取到。
public static int lengthOfLIS(int[] nums) {
if (nums.length <= 1) {
return nums.length;
}
int[] dp = new int[nums.length];
// 初始化
dp[0] = 1;
// 规划
for (int tail = 1; tail < nums.length; tail++) {
int max = 0;
for (int i = 0; i < tail; i++) {
if (nums[tail] > nums[i]) {
max = Math.max(max, dp[i]);
}
}
dp[tail] = max + 1;
}
// 输出dp数组中最大的即可
int max = 1;
for (int i = 0; i < nums.length; i++) {
max = Math.max(max, dp[i]);
}
return max;
}
贪心+二分法
对于此题的进阶,一旦出现logn的时间复杂度,往往可以联想到二分法。
考虑一个简单的贪心,如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。
基于上面的贪心思路,我们维护一个数组 d[i],表示长度为 i 的最长上升子序列的末尾元素的最小值(已知元素),用 len 记录目前最长上升子序列的长度,起始时 len 为 1,d[1]=nums[0]。
同时我们可以注意到 d[i]是关于 i 单调递增的。
我们依次遍历数组nums[] 中的每个元素,并更新数组 d[] 和 len 的值。如果nums[i]>d[len] 则更新 len=len+1,否则在d[1…len]中找满足d[i−1] 根据 d 数组的单调性,我们可以使用二分查找寻找下标 i,优化时间复杂度。 给定两个大小相等的数组 A 和 B,A 相对于 B 的优势可以用满足 A[i] > B[i] 的索引 i 的数目来描述。 贪心算法 我们为什么要在 a > b 时将 a 和 b 配对呢?这是因为此时在 A 中的每张牌都比 b 要大,所以不管我们在 b 前面放置哪张牌都可以得分。我们可以用手中最弱的牌来与 b 配对,这样会使 A 中剩余的牌严格地变大,因此会有更多得分点。 贪心算法 双栈:利用一个临时栈 贪心public static int lengthOfLIS2(int[] nums) {
if (nums.length <= 1) {
return nums.length;
}
int len = 1;
int[] minTail = new int[nums.length+1];
minTail[len] = nums[0];
for (int i = 1; i < nums.length; i++) {
if (len < nums.length && nums[i] > minTail[len]) {
len += 1;
minTail[len] = nums[i];
} else {
int l = 1;
int r = len;
while (l <= r) {
int m = l + (r - l) / 2;
if (minTail[m] < nums[i]) {
l = m + 1;
} else {
r = m - 1;
}
}
minTail[l] = nums[i];
}
}
return len;
}
优势洗牌(https://leetcode-cn.com/problems/advantage-shuffle/)
返回 A 的任意排列,使其相对于 B 的优势最大化。
示例 1:
输入:A = [2,7,11,15], B = [1,10,4,11]
输出:[2,11,7,15]
示例 2:
输入:A = [12,24,8,32], B = [13,25,32,11]
输出:[24,32,8,12]
如果 A 中最小的牌 a 能击败 B 中最小的牌 b,那么我们应当将它们配对。否则, a 将无益于我们的比分,因为它无法击败任何牌。public static int[] advantageCount(int[] A, int[] B) {
// 排序
Arrays.sort(A);
int[] originalB = B.clone();
Arrays.sort(B);
// 找出A中每个元素应该对应哪个B元素
Map
跳跃游戏
public static boolean canJump(int[] nums) {
int rightMost = 0;
for (int i = 0; i < nums.length; i++) {
if (i <= rightMost) {
rightMost = Math.max(rightMost, nums[i] + i);
if (rightMost >= nums.length - 1) {
return true;
}
}else {
break;
}
}
return false;
}
加油站
贪心算法
算法
遍历所有的加油站:public static int canCompleteCircuit(int[] gas, int[] cost) {
int n = gas.length;
int[] save = new int[n];
int totalSave_tank = 0;
int current_tank = 0;
int start = 0;
for (int i = 0; i < n; i++) {
save[i] = gas[i] - cost[i];
totalSave_tank += save[i];
current_tank += save[i];
if(current_tank < 0){
start = i + 1;
current_tank = 0;
}
}
return totalSave_tank >= 0 ? start : -1;
}
用最少数量的箭引爆气球
public int findMinArrowShots(int[][] points) {
Arrays.sort(points, new Comparator
整数拆分
贪心算法
public int integerBreak(int n) {
if(n <= 3){
return n-1;
}
int a = n / 3;
int b = n % 3;
if(b == 0){
return (int) Math.pow(3, a);
}else if(b == 1){
return (int) Math.pow(3, a-1) * 4;
}else {
return (int) Math.pow(3, a) * 2;
}
}
有效的括号字符串
贪心算法
1)遇左括号则至多/至少都增加
2)遇右则至多/至少都减少
若至多 max 都不够抵右括号,则 return false
3)遇 '*' 则可能减少/不变/增多,即 min--; max++;
若至少 min 小于 0 ,说明'*'不能当做右括号处理;所以min减至0,则不可以再减; public boolean checkValidString(String s) {
// possible range
int min = 0, max = 0; // 维护当前左括号的数量范围: [min, max]
for (char c : s.toCharArray()) {
if (c == '(') {
++min;
++max;
} else if (c == ')') {
if (min > 0) min--;
if (max-- == 0) {
return false;// 左括号不够
}
} else {
if (min > 0) {
min--; // 可作为右括号,抵消
}
++max; // 可作为左括号
}
}
return min == 0;
}
public static boolean checkValidString(String s) {
Stack
括号的分数
事实上,我们可以发现,只有 () 会对字符串 S 贡献实质的分数,其它的括号只会将分数乘二或者将分数累加。因此,我们可以找到每一个 () 对应的深度 x,那么答案就是 的累加和。 public int scoreOfParentheses(String S) {
int ans = 0, bal = 0;
for (int i = 0; i < S.length(); ++i) {
if (S.charAt(i) == '(') {
bal++;
} else {
bal--;
if (S.charAt(i-1) == '(')
ans += 1 << bal;
}
}
return ans;
}