貪心算法單獨篇---跟着代碼隨想錄carl學
一、什么是貪心
- 貪⼼的本質是選擇每⼀階段的局部最優,從⽽達到全局最優。
- 這么說有點抽象,來舉⼀個例⼦: 例如,有⼀堆鈔票,你可以拿⾛⼗張,如果想達到最⼤的⾦額,你要怎么拿? 指定每次拿最⼤的,最終結果就是拿⾛最⼤數額的錢。 每次拿最⼤的就是局部最優,最后拿⾛最⼤數額的錢就是推出全局最優。
- 再舉⼀個例⼦如果是 有⼀堆盒⼦,你有⼀個背包體積為n,如何把背包盡可能裝滿,如果還每次選最⼤ 的盒⼦,就不⾏了。這時候就需要動態規划。
1、 貪心的套路(什么時候用貪心)
- 貪⼼算法並沒有固定的套路。 所以唯⼀的難點就是如何通過局部最優,推出整體最優。 那么如何能看出局部最優是否能推出整體最優呢?有沒有什么固定策略或者套路呢? 不好意思,也沒有!
- 靠⾃⼰⼿動模擬,如果模擬可⾏,就可以試⼀試貪⼼策略,如果不可⾏,可能需要 動態規划。
- 如何驗證可不可以⽤貪⼼算法呢? 最好⽤的策略就是舉反例,如果想不到反例,那么就試⼀試貪⼼吧。
2、 貪心一般解題步驟
貪⼼算法⼀般分為如下四步:
- 將問題分解為若⼲個⼦問題
- 找出適合的貪⼼策略
- 求解每⼀個⼦問題的最優解
- 將局部最優解堆疊成全局最優解
真正做題的時候很難分出這么詳細的解題步驟,可能就是因為貪⼼的題⽬往往 還和其他⽅⾯的知識混在⼀起。
二、LeetCode-455.分發餅干
1、題干
2、思路一
- 小餅干給胃口值小的,不浪費。局部最優。
class Solution {
public int findContentChildren(int[] g, int[] s) {
int child=0;
//先給兩個數組從小到大排序
Arrays.sort(g);
Arrays.sort(s);
//拿着餅干去匹配胃口值
for(int i=0;i<s.length;i++){
//如果這個小餅干能滿足,那么就往后,通過child++從而也實現了,滿足了孩子的胃口,計數自增,自己往后移位。
if(child<g.length && s[i]>=g[child]){//有可能會出現越界的情況,加以判斷
child++;
}
}
return child;
}
}
3、思路二
- 大餅干給胃口大的孩子,局部最優。
class Solution {
public int findContentChildren(int[] g, int[] s) {
//先給兩個數組從小到大排序
Arrays.sort(g);
Arrays.sort(s);
int child = g.length-1;//孩子的下標
int cookie = s.length-1;//餅干的下標
int result=0;
//拿着胃口大的孩子去匹配餅干
for(int i=child;i>=0;i--){
if(cookie>=0 && g[i]<=s[cookie] ){//如果這個孩子可以被這個餅干滿足
result++;//結果加一
cookie--;//下一個餅干
}
}
return result;
}
}
三、LeetCode-376.擺動序列
1、題干
2、思路
局部最優:
- 刪除單調坡度上的節點(不包括單調坡度兩端的節點),那么這個坡度就可以有兩個局部峰值。
整體最優:
-
整個序列有最多的局部峰值,從而達到最長擺動序列。
-
局部最優推出全局最優,並且舉不出范例,試試貪心。
實際操作上,其實連刪除的操作都不用做,因為題⽬要求的是最長擺動子序列的長度,所以只需要統計 數組的峰值數量就可以了(相當於是刪除單⼀坡度上的節點,然后統計長度) 這就是貪心所貪的地方,讓峰值盡可能的保持峰值,然后刪除單⼀坡度上的節
坑點:
3、代碼實現
class Solution{
public int wiggleMaxLength(int[] nums) {
//如果小於等於一個
if(nums.length <= 1)
return nums.length;
int curDiff = 0;//當前一對的差值
int preDiff = 0;//前一對的差值
int result = 1;//記錄峰值的個數,序列默認序列最右面有一個峰值
for(int i=0;i<nums.length-1;i++){
curDiff = nums[i+1] - nums[i];
//出現峰值
if((curDiff>0 && preDiff <=0) || (preDiff>=0 && curDiff<0)){
result++;
preDiff = curDiff;
}
}
return result;
}
}
保持區間波動,只需要把單調區間上的元素移除就可以了。
注意峰值=0的情況和序列兩端峰值如何處理的問題。情形要考慮清楚。
四、LeetCode-53.最大子序和
1、題干
2、思路一:暴力迭代
class Solution {
public int maxSubArray(int[] a) {
int i,j;
int sum=0;
int result = Integer.MIN_VALUE;//注意初始值的設置
for(i=0;i<a.length;i++){
sum = 0;
for(j=i;j<a.length;j++){
sum += a[j];
if(sum > result)//上面的初始值妙用
result = sum;
}
}
return result;
}
}
超出時間限制
3、思路二:貪心
-
貪心的點:
-
代碼實現
class Solution {
public int maxSubArray(int[] a) {
int i;
int sum=0;
int result = Integer.MIN_VALUE;
for (i=0;i<a.length;i++){
sum += a[i];
if(sum > result)
result = sum;
if(sum<0)//如果前面的子序列之和小於零,從頭開始,並且result一直維護的時子序列和的最大值。
sum = 0;
}
return result;
}
}
4.思路三:動態規划查表迭代
class Solution {
public int maxSubArray(int[] a) {
int i;
int[] max = new int[a.length];//max[i]記錄到i的最大的子序列
max[0] = a[0];
int result = Integer.MIN_VALUE;
for(i=1;i<a.length;i++){
max[i] = Math.max(a[i],max[i-1]+a[i]);
if(max[i]>result)
result = max[i];
}
return result;
}
}
或者:
class Solution {
public int maxSubArray(int[] a) {
int i;
int[] max = new int[a.length];//max[i]記錄到i的最大的子序列
max[0] = a[0];
for(i=1;i<a.length;i++)
max[i] = Math.max(a[i],max[i-1]+a[i]);
Arrays.sort(max);
return max[a.length-1];
}
}
五、LeetCode-122.買賣股票的最佳時機II
1、題干
2、思路:貪心算法
class Solution {
public int maxProfit(int[] a) {
int[] result = new int[a.length-1];
for(int i=0;i<a.length-1;i++){
result[i] = a[i+1] - a[i];
}
int sum = 0;
for(int i : result){
if(i>0)
sum += i;
}
return sum;
}
}
3、思路二:動態規划
- 不能同時參與多筆交易,因此每天交易結束后只可能存在手里有一支股票或者沒有股票的狀態。
class Solution {
public int maxProfit(int[] a) {
//f[i][1]代表第i天持有的最多現金,手里沒有股票,最大利潤。
//f[i][0]代表第i天持有股票后的最多現金,買了股票后的最大利潤。
int n = a.length;
int[][] f = new int[n][2];
f[0][0] -= a[0];//持有股票
for (int i=1;i<n;i++){
//第i天持股票所剩的最多現金= max(第i-1天持股票所剩現金,第i-1天持現金-買第i天的股票)
f[i][0] = Math.max(f[i-1][0],f[i-1][1]-a[0]);
//第i天持有最多現金= max(第i天持有的最多現金,第i-1天持有股票的最多現金+第i天賣出股票)
f[i][1] = Math.max(f[i-1][1],f[i-1][0]+a[i]);
}
return Math.max(f[n-1][0],f[n-1][1]);
}
}
六、LeetCode-55.跳躍游戲
1、題干
2、思路一:動態規划
圖解:
https://www.cnblogs.com/darkerg/p/15338878.html
class Solution {
public boolean canJump(int[] a) {
int n = a.length;
boolean[] f = new boolean[n];
f[0] = true;
for(int j=1;j<n;j++){
f[j] = false;//假設跳不過去
for(int i=0;i<j;i++){
if(f[i] && (i+a[i])>=j){
f[j] = true;
break;
}
}
}
return f[n-1];
}
}
3、思路二:貪心算法
class Solution {
public boolean canJump(int[] a) {
if(a.length==1)
return true;
int cover = 0;
for(int i=0;i<=cover;i++){//確保了第一次執行
cover = Math.max(i+a[i],cover);//更新覆蓋范圍
if(cover >= a.length-1)
return true;
}
return false;
}
}
七、LeetCode-45.跳躍游戲II
1、題干
2、思路一:貪心
class Solution {
public int jump(int[] a) {
if(a.length==1)
return 0;
int curDistance = 0;//記錄當前這一跳最遠的下標
int nextDistance = 0;//記錄下一條最遠的下標
int step = 0;//走的步數
for(int i=0;i<a.length-1;i++){
nextDistance = Math.max(a[i]+i,nextDistance);//更新下一步最大范圍下標
if(i == curDistance){//當到了當前的最大下標時候
if(curDistance != a.length-1){//當這個下標沒有到終點的時候
step++;//步數加一
curDistance = nextDistance;//更新當前能夠到達的最遠下標
if(nextDistance >= a.length-1) break;//下一步的覆蓋范圍已經可以到達終點
}else
break;
}
}
return step;
}
}
八、LeetCode-1005.K次取反后最大化的數組和
1、題干
2、思路:貪心
解題步驟:
- 將數組按照絕對值大小從大到小排序
- 從前向后遍歷,遇到負數將其變為正數,同時k--
- 如果k還大於0,那么反復轉變數值最小的元素,將k用完
- 求和
新建一個Comparator,用來實現絕對值的比較。
class abster implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
if (Math.abs(o1)>Math.abs(o2))
return 1;
else if (Math.abs(o1)==Math.abs(o2))
return 0;
else
return -1;
}
}
將Int數組轉換成包裝類Integer數組
//將int數組轉換為Integer數組
int[] nums = {1,2,3};
//先將int數組轉換為數值流
IntStream stream = Arrays.stream(nums);
//流中的元素全部裝箱,轉換為流 ---->int轉為Integer
Stream<Integer> integerStream = stream.boxed();
//將流轉換為數組
Integer[] integers = integerStream.toArray(Integer[]::new);//JDK8新特性,兩個冒號代表每一個流里的對象都執行這個方法。
System.out.println(Arrays.toString(integers));
解題主要函數
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
//將int類型數組轉換成Integer類型
//先將int數組轉換為數值流
IntStream stream = Arrays.stream(nums);
//流中的元素全部裝箱,轉換為流 ---->int轉為Integer
Stream<Integer> integerStream = stream.boxed();
//將流轉換為數組
Integer[] a = integerStream.toArray(Integer[]::new);
//按照絕對值大小排序
Arrays.sort(a,new abster());
//從后向前遍歷,遇到負數將其變為正數,同時k--
for(int i=a.length-1;i>=0;i--){
if(a[i]<0 && k>0){
a[i] *= -1;//將負數變成正數
k--;
}
}
//如果k還是大於0,那么讓最小的那個數不斷地反轉
while(k>0){
a[0] *= -1;
k--;
}
int result = 0;
for(int x : a)
result += x;
return result;
}
}
class abster implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
if (Math.abs(o1)>Math.abs(o2))
return 1;
else if (Math.abs(o1)==Math.abs(o2))
return 0;
else
return -1;
}
}
九、LeetCode-134.加油站
1、題干
2、思路一:暴力迭代
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int i;
for(i=0;i<gas.length;i++){
int rest = gas[i] - cost[i];//當前剩余油量
int nextStation = (i+1)%gas.length;
while(nextStation!=i && rest>0){
rest += gas[nextStation]-cost[nextStation];
nextStation = (nextStation+1)%gas.length;
}
if(nextStation==i && rest >= 0)
return i;
}
return -1;
}
}
3、思路二:全局貪心
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int curSum = 0;
int minGas = Integer.MAX_VALUE;//從起點出發油箱里的油量最小值
for(int i = 0;i<gas.length;i++){
int rest = gas[i] - cost[i];//剩余油量
curSum += rest;//當前油量
if(curSum < minGas){
minGas = curSum;//最后累加結果
}
}
if (curSum < 0) return -1;//如果gas總和小於cost的總和,情況一
if (minGas >= 0) return 0;//情況二
//累加的最小值minGas是負數,油不夠,從后往前,看哪個能把它填平。
for (int i = gas.length-1;i>=0;i--){
int rest = gas[i] - cost[i];
minGas += rest;
if(minGas >= 0){
return i;
}
}
return -1;
}
}
- 此種方法沒有局部最優,而是從全局的角度思考問題。
4、思路三:貪心算法-局部最優
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int curSum = 0;
int totalSum = 0;
int start = 0;
for (int i = 0;i < gas.length;i++){
curSum += gas[i] - cost[i];
totalSum += gas[i]-cost[i];
if(curSum < 0){//當前累加rest[i]和curSum一旦小於0
start = i+1;//起始位置更新為i+1
curSum = 0;//curSum從0開始
}
}
if (totalSum < 0) return -1;//說明怎么也跑跑不了一圈
return start;
}
}
十、LeetCode-135.分發糖果
1、題干
2、思路:貪心
這道題⽬⼀定是要確定⼀邊之后,再確定另⼀邊,例如⽐較每⼀個孩⼦的左邊,然后再⽐較右邊,如果兩邊⼀起考 慮⼀定會顧此失彼
①右邊評分大於左邊的情況(也就是從前向后遍歷)
//從前向后
for(int i = 1;i<ratings.length;i++){
if(ratings[i] > ratings[i-1])
candyVec[i] = candyVec[i-1] + 1;
}
②左孩子大於右孩子的情況一定要從后往前去遍歷
代碼如下:
//從后向前
for(int i = ratings.length-2;i>=0;i--){
if (ratings[i] > ratings[i+1]){
candyVec[i] = Math.max(candyVec[i],candyVec[i+1]+1);
}
}
③總體代碼
class Solution {
public int candy(int[] ratings) {
int[] candyVec = new int[ratings.length];
for(int i=0;i<candyVec.length;i++)
candyVec[i] = 1;
//從前往后
for (int i = 1;i<ratings.length;i++){
if(ratings[i] > ratings[i-1]){
candyVec[i] = candyVec[i-1]+1;
}
}
//從后往前
for (int i = ratings.length-2;i>=0;i--){
if(ratings[i] > ratings[i+1]){
candyVec[i] = Math.max(candyVec[i],candyVec[i+1]+1);
}
}
//結果
int result = 0;
for(int i=0;i<candyVec.length;i++)
result += candyVec[i];
return result;
}
}
3、反思:
- 在考慮問題的時候,如果一直想兩邊一次都解決,就會顧此失彼,調入陷阱。
- 采用兩次貪心策略:
- 一次從左到右遍歷,只比較右邊孩子評分比左邊大的情況。
- 一次從右往左遍歷,之比較左邊孩子評分比右邊大的情況。
- 如此一來就從局部最優,推出了全局最優,即相鄰的孩子中,評分最高的孩子獲得更多的糖果。
十一、LeetCode-860.檸檬水找零
1、題干
2、思路
class Solution {
public boolean lemonadeChange(int[] bills) {
int five = 0;
int ten = 0;
int twenty = 0;
for (int i=0;i<bills.length;i++){
//如果有付五塊錢的
if (bills[i] == 5)
five++;
//如果有付十塊錢的
if (bills[i] == 10){
five--;
ten++;
}
//如果有付二十塊的
if (bills[i] == 20){
if(ten>0){
ten--;
five--;
}else{
five -= 3;
}
}
if(five<0 || ten<0)
return false;
}
return true;
}
}
十二、LeetCode-406.根據身高重建隊列
1、題干
2、思路
public class Solution {
public int[][] reconstructQueue(int[][] people) {
Arrays.sort(people, new Comparator<int[]>() {
@Override
/**
* compare默認升序排列,如果person1比person2大的話,那么返回1,person1往后。
*/
public int compare(int[] person1, int[] person2) {
if (person1[0] != person2[0]){//如果身高不相等的話,此時做的是降序排列身高從高到低。
return person2[0] - person1[0];
}else{
return person1[1] - person2[1];//按照前面有多少個升序排列,所以用person1-person2
}
}
});
List<int[]> ans = new ArrayList<>();
for (int[] person : people) {
ans.add(person[1],person);
}
return ans.toArray(new int[ans.size()][]);
}
}
3、語法基礎回顧
①List接口中的add()方法
public class Main {
public static void main(String[] args) {
Set<String> stringSet = new HashSet<>();
stringSet.add("string1");
stringSet.add("string2");
stringSet.add("string3");
List<String> stringList = new ArrayList<>();
stringList.addAll(0, stringSet);
System.out.println(stringList);
}
}
十三、LeetCode-452.用最少數量的箭引爆氣球
1、題干
2、思路
可以看出⾸先第⼀組重疊⽓球,⼀定是需要⼀個箭,⽓球3,的左邊界⼤於了 第⼀組重疊⽓球的最⼩右邊界,所以 再需要⼀⽀箭來射⽓球3了。
class Solution {
public int findMinArrowShots(int[][] points) {
Arrays.sort(points, new Comparator<int[]>() {
@Override
public int compare(int[] b1, int[] b2) {
if( b1[0] < b2[0])
return -1;
else if(b1[0] == b2[0])
return 0;
else
return 1;
}
});
int result = 1;//最少需要一支箭
for (int i=1;i<points.length;i++){
if (points[i][0] > points[i-1][1]){//這倆氣球不相鄰
result++;
}else {
points[i][1] = Math.min(points[i-1][1],points[i][1]);//更新重疊氣球最小右邊界
}
}
return result;
}
}
十四、LeetCode-435.無重疊區間
1、題干
2、思路:計算重復區間的問題
和上一道射箭的題一樣,只是在不重復區間這個問題上做了一些改動,如果邊界相等,也算作不重疊。
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] b1, int[] b2) {
if( b1[0] < b2[0])
return -1;
else if(b1[0] == b2[0])
return 0;
else
return 1;
}
});
int result = 0;
for(int i=1;i<intervals.length;i++){
if(intervals[i][0] > intervals[i-1][1]){
}else if(intervals[i][0] != intervals[i-1][1]){
intervals[i][1] = Math.min(intervals[i-1][1],intervals[i][1]);
result++;
}
}
return result;
}
}
十五、LeetCode-763.划分字母區間
1、題干
2、思路:
class Solution {
public List<Integer> partitionLabels(String s) {
int[] loc = new int[26];
for (int i = 0;i < 26;i++){
loc[i] = s.lastIndexOf((char)97+i);
}
List<Integer> result = new ArrayList<>();
int right = 0;
int left = 0;
for (int i = 0;i<s.length();i++){
right = Math.max(right,loc[(int)s.charAt(i) - 97]);
if (i==right){
result.add(right-left+1);
left = right + 1;
}
}
return result;
}
}
3、String類和StringBuffer類常用方法
String類
1.獲取:
1)獲取字符串str長度
int i = str.length();
2)根據位置(index)獲取字符
char c = str.charAt(index);
3)獲取字符在字符串中的位置
int i =str.indexOf(char ch); //獲取的是第一次出現的位置
int i =str.indexOf(char ch ,int index); //從位置index后獲取ch出現的第一次的位置
int i =str.indexOf(str1) ;// 獲取str1 在str 第一次出現的位置
int i=str.indexOf(str1, index0);//獲取從index位置后str第一次出現的位置
int i = str.lastIndexOf(ch或者 str1) //獲取ch或者str1最后出現的位置
2.判斷
1)判斷是否以指定字符串str1開頭、結尾
boolean b = str.startWith(str1) //開頭
boolean b = str.endsWith(str1) //結尾
2)判斷是否包含某一子串
boolean b = str.contains(str1)
3)判斷字符串是否有內容
boolean b = str.isEmpty();
4)忽略大小寫判斷字符串是否相同
boolean b = str.equalsIgnoreCase(str1);
3.轉換
1)將字符數組 -char[] ch- 轉化成字符串
i. String str =new String(ch); //將整個數組變成字符串
ii. String str =new String(ch,offset,count)
//將字符數組中的offset位置之后的count個元素轉換成字符串
1. String str =String.valueOf(ch);
2. String str =String.copyValueOf(ch,offset,count);
3. String str =String.copyValueOf(ch);
2)將字符串轉化為字符數組
char[] ch = str.toCharAarray();
3)將字節數組轉換為字符串
同上1) 傳入類型變為Byte[];
4)將字符串轉換為字節數組
Byte[] b = str.toByteArray();
5)將基本數據類型裝換成字符串
String str = String.valueOf(基本數據類型數據);
若是整形數據可以用 字符串連接符 + ""
eg : String str = 5+"";
得到字符串 “5”
4.替換 replace();
str.replace(oldchar,newchar)//將str里oldchar變為newchar
str.replace(str1,str2)//將str中str1,變為str2
5.切割 split();
String[] str1 = str.split(","); //將str用 ","分割成String數組
6.子串
String s = str.substring(begin);
// s 為 str 從begin位置到最后的字符串
String s = str.substring(begin,end)
//s 是 str 從begin 位置到end 位置的字符串
7.轉換大小寫:
String s1 = str. toUpperCase(); //將str變成大寫字母
String s2 = str. toLowerCase(); //將str變成小寫字母
除去空格:
String s =str.trim();
比較:
int i = str.compareTo(str1);
StringBuffer類
/***StringBuffer 是一個容器,長度可變,可以直接操作字符串,用toString方法變為字符串 **/
1.存儲
1)append(); //將指定數據加在容器末尾,返回值也是StringBuffer
eg:
StringBuffer sb = new StringBuffer(//可以加str);
StringBuffer sb1=ab.append(數據) //數據可以任何基本數據類型
注:此時sb == sb1他們是同一對象,意思是可以不用新建sb1直接 sb.append(數據) 使用時之后接使用sb
2)insert();// 插入
sb.insert(index ,數據);
2.刪除
sb.delete(start ,end); //刪除start到end的字符內容
//注意:這里的所有包含index的操作都是含頭不含尾的
sb.deleteCharAt(index);//刪除指定位置的字符
//清空StringBuffer緩沖區
sb=new StringBuffer();
sb.delete(0,sb.length());
3.獲取
char c = sb.charAt(index);//獲取index上的字符
int i = sb.indexOf(char)://獲取char字符出現的第一次位置
//與 String 中的獲取方法一致參考前面
4.修改 String類中無次操作方法
sb =sb.replace(start,end,string)//將從start開始到end的字符串替換為string;
sb.setCharAr(index ,char);//將index位置的字符變為新的char
5.反轉 sb.reverse();//將sb倒序
6. getChars(int srcBegin,int srcEnd,char[] ch,int chBegin)
//將StringBuffer緩沖區中的指定數據存儲到指定數組中
十六、LeetCode-56.合並區間
1、題干
2、思路:
思路和前面的區間重疊問題一樣,先對區間從左到右排序,左區間值小的靠左。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
class Solution {
public static int[][] merge(int[][] intervals) {
//給區間從小到大排序
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] b1, int[] b2) {
if( b1[0] < b2[0])
return -1;
else if(b1[0] == b2[0])
return 0;
else
return 1;
}
});
//存儲結果
List<int[]> result = new ArrayList<>();
//先把前一個放進去
result.add(intervals[0]);
//判斷區間是否重疊
for (int i=1;i<intervals.length;i++){
//如果后一個和前一個不重疊,把當前這個也放進去
if (intervals[i][0] > intervals[i-1][1]){
result.add(intervals[i]);
}else {//如果當前和前一個重疊
int right = Math.max(intervals[i][1],intervals[i-1][1]);
//把當前這個放進去
intervals[i][1] = right;
intervals[i][0] = intervals[i-1][0];
result.set(result.size()-1,intervals[i]);
}
}
int[][] x = result.toArray(new int[result.size()][]);
return x;
}
}
十七、LeetCode-738.單調遞增的數字
1、題干
2、思路:暴力解法
class Solution {
public int monotoneIncreasingDigits(int n) {
for (int i = N;i > 0 ;i--)
if(checkNum(i)) return i;
return 0;
}
public boolean checkNum(int num){
int max = 10;
while(num){
int t = num % 10;//取最后一位,應該是最大的。
if (max >= t) max=t;
else return false;
num = num/10;
}
return true;
}
}
3、思路二:貪心
import java.util.Vector;
class Solution {
public int monotoneIncreasingDigits(int n) {
//使用Vector來存儲,相當於動態數組
Vector<Integer> target = new Vector<>();
while (n!=0){
target.add(0,n%10);
n /= 10;
}
//看一下從多會兒開始后面的全部賦值成9
int flag = target.size();
for (int i=target.size()-1;i>0;i--){
if (target.get(i-1) > target.get(i)){
flag = i;
target.setElementAt(target.get(i-1)-1,i-1);
}
}
for (int i = flag;i<target.size();i++)
target.setElementAt(9,i);
for (Integer integer : target) {
System.out.println(integer);
}
int sum = 0;
for (int i = 0;i<target.size();i++){
sum += target.get(i)*Math.pow(10,target.size()-i-1);
}
return sum;
}
}
官方解
class Solution {
public int monotoneIncreasingDigits(int n) {
char[] strN = Integer.toString(n).toCharArray();
int i = 1;
while (i < strN.length && strN[i - 1] <= strN[i]) {
i += 1;
}
if (i < strN.length) {
while (i > 0 && strN[i - 1] > strN[i]) {
strN[i - 1] -= 1;
i -= 1;
}
for (i += 1; i < strN.length; ++i) {
strN[i] = '9';
}
}
return Integer.parseInt(new String(strN));
}
}
十八、LeetCode-714.買賣股票的最佳時機含手續費
1、題干
2、思路一:貪心
class Solution {
public int maxProfit(int[] prices, int fee) {
int result = 0;
int minPrice = prices[0];//記錄最低價格
for (int i=1;i<prices.length;i++){
//情況二:相當於買入
if(prices[i] < minPrice) minPrice = prices[i];
//情況三:保持原有狀態(因為此時買不便宜,賣的話虧本)
if (prices[i] >= minPrice && prices[i] <= minPrice + fee);
//計算利潤,可能有多次計算利潤,最后一次計算利潤才是真正意義的賣出
if (prices[i] > minPrice + fee){//如果有利潤
result += prices[i] - minPrice -fee;
minPrice = prices[i] - fee;//情況一,這一步很關鍵。
}
}
return result;
}
}
- 注意情況一的操作,因為如果還在收獲利潤的區間里,表示並不是真正的賣出,而計算利潤每次都要減去手續費,所以要讓minPrice=prices[i]-fee,這樣在明天收獲利潤的時候,才不會多減一次手續費。
- 例如:1 3 2 8 9 4,在8和9的地方。
3、思路二:動態規划
class Solution {
public int maxProfit(int[] prices, int fee) {
int n = prices.length;
int[][] dp = new int[n][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < n; ++i) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[n - 1][0];
}
}
class Solution {
public int maxProfit(int[] prices, int fee) {
int n = prices.length;
int sell = 0, buy = -prices[0];
for (int i = 1; i < n; ++i) {
sell = Math.max(sell, buy + prices[i] - fee);
buy = Math.max(buy, sell - prices[i]);
}
return sell;
}
}