双指针
贪心算法
class Solution {
public int maxArea(int[] height) {
int l = 0;
int r = height.length - 1;
int max = 0;
while (l < r){
int h = Math.min(height[l],height[r]);
max = Math.max((r - l) * h,max);
if (height[l] > height[r])r--;//贪心选择
else l++;
}
return max;
}
}
贪心算法
动态维护可以到达的最远位置
class Solution {
public boolean canJump(int[] nums) {
int max = 0;//维护最远可到达的位置
for (int i = 0; i < nums.length; i++) {
max = Math.max(max,i + nums[i]);
if (max >= nums.length - 1)return true;
if (max <= i)return false;
}
return false;
}
}
方法一:贪心算法 时间复杂度O(N2)
class Solution {
/*
贪心算法
dp[i] = Math.min(dp[0],dp[1],dp[2]...dp[i - 1]) + 1
*/
public int jump(int[] nums) {
//dp[i] 跳到位置i的最少次数
int[] dp = new int[nums.length];
dp[0] = 0;
for (int i = 1; i < nums.length; i++) {
int min = Integer.MAX_VALUE;
for (int j = 0; j < i; j++) {
if (nums[j] + j >= i){
min = Math.min(min,dp[j]);
}
}
dp[i] = min + 1;
}
return dp[nums.length - 1];
}
}
方法二:贪心算法 时间复杂度O(N2)
class Solution {
/*
贪心算法:
从尾到前查找可以到达当前位置的最前面的一个位置
*/
public int jump(int[] nums) {
int cur = nums.length - 1;
int count = 0;
while (cur > 0){
int minIdx = Integer.MAX_VALUE;
for (int i = 0; i < cur; i++) {
if (i + nums[i] >= cur)minIdx = Math.min(minIdx,i);
}
count++;
cur = minIdx;
}
return count;
}
}
方法三:
贪心算法
从前向后寻找可以到达的最远位置
时间复杂度O(N)
class Solution {
/*
贪心算法:
从前到后查找可以到达的最远位置
*/
public int jump(int[] nums) {
int n = nums.length;
int end = 0;
int maxPosition = 0;
int step = 0;
for (int i = 0; i < n - 1; i++) {
maxPosition = Math.max(maxPosition,i + nums[i]);
if (i == end){
step++;
end = maxPosition;
}
}
return step;
}
}
方法一:动态规划
时间复杂度O(N)
空间复杂度O(N)
此题空间复杂度可进一步优化
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
//dp[i][0] 第i天持有股票的最大利润
//dp[i][1] 第i天未持有股票的最大利润
int[][] dp = new int[n][2];
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (int i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i - 1][0],dp[i - 1][1] - prices[i]);
dp[i][1] = Math.max(dp[i - 1][1],dp[i - 1][0] + prices[i]);
}
return Math.max(dp[n - 1][0],dp[n - 1][1]);
}
}
方法二:单调栈
贪心算法
单调递增栈,有盈利就买卖股票
class Solution {
public int maxProfit(int[] prices) {
Stack<Integer> stack = new Stack<>();
int ans = 0;
stack.push(prices[0]);
for (int i = 1; i < prices.length; i++) {
while (!stack.isEmpty() && prices[i] <= stack.peek())stack.pop();
while (!stack.isEmpty() && prices[i] > stack.peek()){
ans += prices[i] - stack.pop();
}
stack.push(prices[i]);
}
return ans;
}
}
一次遍历 数学证明
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int n = gas.length;
int i = 0;
// 从头到尾遍历每个加油站,并且检查以该加油站为起点,能否行驶一周
while(i < n){
int sumOfGas = 0; // 总共加的油
int SumOfCost = 0; // 总共消费的油
int count = 0; // 记录能走过几个站点
while(count < n){ // 退出循环的条件是走过所有的站点
int j = (i + count) % n; // 加油站是环形的
sumOfGas += gas[j];
SumOfCost += cost[j];
if(SumOfCost > sumOfGas){ // 如果这个站点发现油不够了
break;
}
count++; // 这个站点满足情况
}
if(count == n){ // 如果能环绕一圈
return i;
}else{ // 不行的话 从下一个站点开始 检查
i = i + count + 1;
}
}
// 所有加油站作为起点都不满足
return -1;
}
}
排序
class Solution {
public String largestNumber(int[] nums) {
//转换为对象类型,对象类型才可以自定义排序规则
Integer[] arr = new Integer[nums.length];
for (int i = 0; i < nums.length; i++) {
arr[i] = nums[i];
}
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
String s1 = "" + o1 + o2;
String s2 = "" + o2 + o1;
return s2.compareTo(s1);
}
});
StringBuilder res = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
res.append(arr[i]);
}
//去除前导0
while (res.length() > 0 && res.charAt(0) == '0')res.deleteCharAt(0);
if (res.length() == 0)return "0";//去除前导0
return res.toString();
}
}
贪心算法 + 单调栈
class Solution {
public String removeDuplicateLetters(String s) {
int n = s.length();
char[] chars = s.toCharArray();
int[] lastIdx = new int[26];//维护每个字母最后出现的一次位置
for (int i = 0; i < n; i++) {
lastIdx[chars[i] - 'a'] = i;
}
Deque<Character> stack = new ArrayDeque<>();//维护单调栈
boolean[] isvisted = new boolean[26];//维护某个字母在栈中是否出现过
for (int i = 0; i < n; i++) {
if (isvisted[chars[i] - 'a'])continue;//如果当前字符出现过
while (!stack.isEmpty() && stack.peekLast() > chars[i] && lastIdx[stack.peekLast() - 'a'] > i){//维护递增栈
Character la = stack.removeLast();
isvisted[la - 'a'] = false;
}
stack.addLast(chars[i]);;
isvisted[chars[i] - 'a'] = true;
}
StringBuilder res = new StringBuilder();
for (Character c:stack) {
res.append(c);
}
return res.toString();
}
}
方法一:双向遍历
寻找下标 i 处左边的最小值与右边的最大值,通过一次遍历得到
思想是否可以拆分时间复杂度为O(N2)的算法为O(N)
class Solution {
//时间复杂度n,空间复杂度n
public boolean increasingTriplet(int[] nums) {
int n = nums.length;
int[] leftmin = new int[n];//leftmin[i] : 维护下标i处左侧最小值
int[] rightmax = new int[n];//rightmax[i] : 维护下标i处右侧最大值
int min = nums[0];
for (int i = 1; i < n; i++) {
leftmin[i] = min;
min = Math.min(min,nums[i]);
}
int max = nums[n - 1];
for (int i = n - 2; i >= 0; i--) {
rightmax[i] = max;
max = Math.max(max,nums[i]);
}
for (int i = 1; i < n - 1; i++) {
if (nums[i] > leftmin[i] && nums[i] < rightmax[i])return true;
}
return false;
}
}
方法二:贪心算法
时刻维护两个变量 first与second,且first < second
class Solution {
public boolean increasingTriplet(int[] nums) {
int first = nums[0];
int second = Integer.MAX_VALUE;
for (int i = 0; i < nums.length; i++) {
if (nums[i] > second)return true;
else if (nums[i] > first)second = nums[i];
else first = nums[i];
}
return false;
}
}
方法一:动态规划
class Solution {
//某个序列被称为「上升摆动序列」,当且仅当该序列是摆动序列,且最后一个元素呈上升趋势。
//某个序列被称为「下降摆动序列」,当且仅当该序列是摆动序列,且最后一个元素呈下降趋势。
public int wiggleMaxLength(int[] nums) {
int n = nums.length;
int[] up = new int[n];//up[i] 表示以前 iii 个元素中的某一个为结尾的最长的「上升摆动序列」的长度。
int[] down = new int[n];//down[i] 表示以前 iii 个元素中的某一个为结尾的最长的「下降摆动序列」的长度。
up[0] = down[0] = 1;
for (int i = 1; i < n; i++) {
if (nums[i] == nums[i - 1]){//相等则维持不变
up[i] = up[i - 1];
down[i] = down[i - 1];
}else if (nums[i] < nums[i - 1]){
up[i] = up[i - 1];
down[i] = Math.max(down[i - 1],up[i - 1] + 1);
}else if (nums[i] > nums[i - 1]){
down[i] = down[i - 1];
//down[i - 1]序列中的最后一个元素小于nums[i]
up[i] = Math.max(up[i],down[i - 1] + 1);
}
}
return Math.max(up[n - 1],down[n - 1]);
}
}
方法二:贪心算法
交错的选择峰与谷可以得到最好的结果
class Solution {
public int wiggleMaxLength(int[] nums) {
int n = nums.length;
if (n < 2) {
return n;
}
int prevdiff = nums[1] - nums[0];
int ret = prevdiff != 0 ? 2 : 1;
for (int i = 2; i < n; i++) {
int diff = nums[i] - nums[i - 1];
if ((diff > 0 && prevdiff <= 0) || (diff < 0 && prevdiff >= 0)) {
ret++;
prevdiff = diff;
}
}
return ret;
}
}
暴力法想到了,懒得实践所以未做出来。
贪心算法未想到。
方法一:递归
class Solution {
public int integerReplacement(int n) {
if (n == 1) {
return 0;
}
if (n % 2 == 0) {
return 1 + integerReplacement(n / 2);
}
return 2 + Math.min(integerReplacement(n / 2), integerReplacement(n / 2 + 1));
}
}
方法二:记忆化搜索
对方法一进行优化
class Solution {
Map<Integer, Integer> memo = new HashMap<Integer, Integer>();
public int integerReplacement(int n) {
if (n == 1) {
return 0;
}
if (!memo.containsKey(n)) {
if (n % 2 == 0) {
memo.put(n, 1 + integerReplacement(n / 2));
} else {
memo.put(n, 2 + Math.min(integerReplacement(n / 2), integerReplacement(n / 2 + 1)));
}
}
return memo.get(n);
}
}
方法三:贪心算法 (位运算)
class Solution {
public int integerReplacement(int _n) {
long n = _n;
int ans = 0;
while (n != 1) {
if (n % 2 == 0) {
n >>= 1;
} else {
if (n != 3 && ((n >> 1) & 1) == 1) n++;
else n--;
}
ans++;
}
return ans;
}
}
贪心算法
当有两个维度的变化时一般会用到一个维度升序一个维度降序的解题思路,
先确定一个维度,再去思考另一个维度。
class Solution {
public int[][] reconstructQueue(int[][] people) {
//排序
Arrays.sort(people,(p,p2) -> {
//按身高递增排序
if (p[0] != p2[0])return p[0] - p2[0];
//按每个人前有几人降序排序
return p2[1] - p[1];
});
int n = people.length;
int[][] res = new int[n][];
//for循环的意义
//为每个人前留出k个空,等待比p高的人插入
for (int[] p:people
) {
int step = p[1] + 1;
for (int j = 0; j < n; j++) {
if (res[j] == null){
step--;
if (step == 0)res[j] = p;
}
}
}
return res;
}
}
class Solution {
public int longestPalindrome(String s) {
char[] chars = s.toCharArray();
Map<Character,Integer> map = new HashMap<>();
//统计每个字符出现的个数
for (int i = 0; i < chars.length; i++) {
map.put(chars[i],map.getOrDefault(chars[i],0) + 1);
}
Set<Map.Entry<Character, Integer>> entries = map.entrySet();
boolean flag = false;
int res = 0;
//对于出现偶数次的字符全部都要
//对于出现奇数次的字符只要每个字符的 n - 1个,n为该字符出现的次数
//如果存在奇数字符结果要加1
for (Map.Entry<Character, Integer> item:entries
) {
int num = item.getValue();
if (num % 2 == 1)flag = true;
res += (num % 2 == 1 ? num - 1 : num);
}
return res = flag ? res + 1 : res;
}
}
贪心算法
当有两个维度时一般一个升序一个降序
class Solution {
public int findMinArrowShots(int[][] points) {
Arrays.sort(points,(o,o2) -> {
long n = o[0];
long n2 = o2[0];
if (n != n2){
return n - n2 > 0 ? 1 : -1;
}else {
long n3 = o[1];
long n4 = o2[1];
return n3 - n4 == 0 ? 0 : n3 - n4 > 0 ? 1 : -1;
}
});
int end = points[0][1];//end的初始值
int count = 1;
for (int i = 0; i < points.length; i++) {
if (end < points[i][0]){
end = points[i][1];
count++;
}else {//更新能到达的最远端
end = Math.min(end,points[i][1]);
}
}
return count;
}
}
方法一:暴力
/*
暴力寻找start与end
start:最靠前的一个待排序元素
end:最靠后的一个待排序元素
*/
class Solution {
public int findUnsortedSubarray(int[] nums) {
int start = -1;
int end = -1;
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] > nums[j] && start == -1){
start = i;
end = j;
}else if (nums[i] > nums[j]){
start = Math.min(start,i);
end = Math.max(end,j);
}
}
}
if (start == -1)return 0;
return end - start + 1;
}
}
方法二:贪心算法
利用排序找规律
/*
贪心算法:
排序:将数组nums分为三个子数组numsa,numsb,numsc
numsa,numsc为有序数组,numsb为无序数组,对numsa与numsc经过nums排序后元素的位置不变
将numsa与nums比较得到最长前缀的长度
将numsb与nums比较得到最长后缀的长度
*/
class Solution {
public int findUnsortedSubarray(int[] nums) {
int[] arr = Arrays.copyOf(nums, nums.length);
Arrays.sort(arr);
int preCount = 0;
int postCount = 0;
for (int i = 0; i < arr.length; i++) {
if (nums[i] == arr[i])preCount++;
else break;
}
for (int i = arr.length - 1; i >= 0; i--) {
if (nums[i] == arr[i])postCount++;
else break;
}
if (preCount == nums.length)return 0;
return nums.length - preCount - postCount;
}
}
贪心算法
class Solution {
public boolean canPlaceFlowers(int[] flowerbed, int n) {
int count = 0;
int m = flowerbed.length;
int prev = -1;//维护 prev表示上一朵已经种植的花的下标位置,初始时 prev=−1,表示尚未遇到任何已经种植的花。
//寻找两朵花之间最多可以插入几多花
for (int i = 0; i < m; i++) {
if (flowerbed[i] == 1) {
if (prev < 0) {//第一朵花之前可以插入几朵花
count += i / 2;
} else {
//两朵花之间最多可插入 (i - prev - 2) / 2 朵花
count += (i - prev - 2) / 2;
}
prev = i;
}
}
if (prev < 0) {
count += (m + 1) / 2;
} else {//最后一朵花之后可以插入多少朵花
count += (m - prev - 1) / 2;
}
return count >= n;
}
}
贪心算法
class Solution {
public int findLongestChain(int[][] pairs) {
if (pairs.length == 0)return 0;
//排序使右边界尽可能小
Arrays.sort(pairs,(o1, o2) -> {
return o1[1] - o2[1];
});
int ans = 1;
int pre = 0;
for (int i = 1; i < pairs.length; i++) {
if (pairs[i][0] > pairs[pre][1]){
ans++;
pre = i;
}
}
return ans;
}
}
方法一:hash + 优先级队列
class Solution {
public boolean isPossible(int[] nums) {
Map<Integer, PriorityQueue<Integer>> map = new HashMap<Integer, PriorityQueue<Integer>>();
//map的key存放子序列的最后一个值
//map的值存放以key结尾的每一个子序列的长度
for (int x: nums) {
if (!map.containsKey(x))map.put(x,new PriorityQueue<>());
if (map.containsKey(x - 1)){
Integer len = map.get(x - 1).poll();
if (map.get(x - 1).isEmpty())map.remove(x - 1);
map.get(x).offer(len + 1);
}else {
map.get(x).offer(1);
}
}
Set<Integer> set = map.keySet();
for (int key:set) {
PriorityQueue<Integer> queue = map.get(key);
if (queue.poll() < 3)return false;
}
return true;
}
}
方法二:贪心算法
class Solution {
public boolean isPossible(int[] nums) {
Map<Integer,Integer> mapKeyCount = new HashMap<>();
Map<Integer,Integer> mapEndNumCount = new HashMap<>();
//统计每个key的数量
for(int n: nums)mapKeyCount.put(n,mapKeyCount.getOrDefault(n,0) + 1);
for (int i = 0; i < nums.length; i++) {
int n = nums[i];
if (mapKeyCount.get(n) == 0)continue;//待放进序列的数字必须存在
//if 贪心选择,尽量不要创建长度小于3的序列
//else 短序列也至少能构成长度为3的序列
if (mapEndNumCount.containsKey(n - 1) && mapEndNumCount.get(n - 1) != 0){//存在以n - 1结尾的子序列
mapKeyCount.put(n,mapKeyCount.get(n) - 1);
mapEndNumCount.put(n - 1,mapEndNumCount.get(n - 1) - 1);
mapEndNumCount.put(n,mapEndNumCount.getOrDefault(n,0) + 1);
}else {//不存在以n-1结尾的子序列
//如果不存在连续的三个数字,则返回false
if (!mapKeyCount.containsKey(n + 1) || !mapKeyCount.containsKey(n + 2)
|| mapKeyCount.get(n + 1) == 0 || mapKeyCount.get(n + 2) == 0)return false;
mapKeyCount.put(n,mapKeyCount.get(n) - 1);
mapKeyCount.put(n + 1,mapKeyCount.get(n + 1) - 1);
mapKeyCount.put(n + 2,mapKeyCount.get(n + 2) - 1);
mapEndNumCount.put(n + 2,mapEndNumCount.getOrDefault(n + 2,0) + 1);
}
}
return true;
}
}
class Solution {
public int maximumSwap(int num) {
String str = num + "";
char[] chars = str.toCharArray();
int idx = 0;
int idx2 = 0;
for (int i = 0; i < chars.length; i++) {
idx = i;
idx2 = i;
int max = chars[i];
for (int j = i + 1; j < chars.length; j++) {
//贪心选择:尽量把大的数字移到前面来,并且如果有相同大小的数字选择后面的数字
if (chars[j] >= max){
idx2 = j;
max = chars[j];
}
}
//如果本轮存在有效换位,后面有更大的数字,则提前结束
if (chars[idx] != chars[idx2] && idx != idx2)break;
}
swap(chars,idx,idx2);
return Integer.parseInt(new String(chars));
}
public void swap(char[] chars,int idx,int idx2){
char item = chars[idx2];
chars[idx2] = chars[idx];
chars[idx] = item;
}
}