徒手挖地球二七周目

徒手挖地球二七周目

NO.60 第K个排列 中等

在这里插入图片描述
思路一:逆用康托展开 什么是康托展开式可以先从百度了解。本文直接从示例着手。

利用康托展开求排列是第几个,例如"213"是n=3的全排列中的第几个?[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZqMsYQkZ-1583742743249)(https://s2.ax1x.com/2020/03/08/3x5LgH.png)]

其实就是找出比"213"小的排列有几个,然后+1。

先比较首位,比2小的只有1,1开头的排列有1*2!个。

第二位比1小的不存在所以有0*1!排列。

第三位比3小的有1和2但是前面已经用过所以有0*0!个。

综上所述,有2个比"213"小的排列,所以"213"排在第3位。

本题是逆用,知道n和第k个,求排列本身。

例如:要找n=5,k=35的排列。共有5!=120个排列,从小到大要找第35个排列。

从首位开始找,首位分别是1、2、3、4、5的排列分成五组,每组各有(5-1)!=24个,由于k=35,所以要找的排列在第二组中,就是首位是2开头的24个排列中,即目标排列首位是"2"。

再看第二位,第二位可能是1、3、4、5分成四组,每组分别有(5-2)!=6个,由于k-24=11,所以要找的排列在第二组中,即目标排列的前两位是"23"。

同理,第三位可能是1、4、5分成三组,每组分别有(5-3)!=2个,因为k-6=5,所以要找的排列在第三组的两个排列中,即目标排列的前三位是"235"。

再然后第四位可能是1、4,分两组各有(5-4)!=1个,因为k-4=1,所以在第一组,即目标排列的前四位是"2351"。

最后第五位只剩下4,所以目标排列是"23514"。

找到第35个排列。

其实这个方法就是分组,上例中找第一位时候将有序全排列按照首位数字分为5组,看看我们要找的那个排列在第几组中从而确定了首位数字。

public String getPermutation(int n, int k) {
    StringBuilder ans=new StringBuilder();
    //所有可用数字
    StringBuilder map=new StringBuilder();
    for (int i = 1; i <= n ; i++)map.append(i);
    int groupId,index=n;
    //逐位确定
    for (int i = 1; i <= n; i++) {
        index--;
        //确定在当前分组的第几组
        groupId=(k-1)/foc(index);
        //得到分组的数字,移除使用过的数字
        ans.append(map.charAt(groupId));
        map.deleteCharAt(groupId);
        //更新k
        k-=groupId*foc(index);
    }
    return ans.toString();
}
// 求 i!
private int foc(int i) {
    int x=1;
    while (i>=1){
        x*=i--;
    }
    return x;
}

时间复杂度:O(n^2) 题目说n的范围[1,9],可以直接将1~9的阶乘预先保存在一个数组中。

public String getPermutation(int n, int k) {
    StringBuilder ans=new StringBuilder();
    //0~9的阶乘,直接使用
    int factor[] = {1,1,2,6,24,120,720,5040,40320,362880};
    //所有可用数字
    StringBuilder map=new StringBuilder();
    for (int i = 1; i <= n ; i++)map.append(i);
    int groupId,index=n;
    //逐位确定
    for (int i = 1; i <= n; i++) {
        index--;
        //确定在当前分组的第几组
        groupId=(k-1)/factor[index];
        //得到分组的数字,移除使用过的数字
        ans.append(map.charAt(groupId));
        map.deleteCharAt(groupId);
        //更新k
        k-=groupId*factor[index];
    }
    return ans.toString();
}

时间复杂度:O(n)

NO.61 旋转链表 中等

在这里插入图片描述
思路一:成环再断 先将链表首尾相连形成环,然后找到旋转后的新链表的新头节点和尾节点,从两者之间断开形成需要的结果。

8prla4.png
在这里插入图片描述

public ListNode rotateRight(ListNode head, int k) {
    if(head==null||head.next==null)return head;
    //标记初始链表的结尾,n记录节点数
    ListNode oldTail=head;
    int n=1;
    while (oldTail.next!=null){
        oldTail=oldTail.next;
        n++;
    }
    //链表成环
    oldTail.next=head;
    //旋转之后的新结尾
    ListNode newTail=head;
    for (int i = 1; i < n - k % n; i++) {
        newTail=newTail.next;
    }
    //新头节点
    ListNode newHead=newTail.next;
    //新尾节点和新头节点之间断开
    newTail.next=null;
    return newHead;
}

时间复杂度:O(n)

NO.121 买卖股票的最佳时机 简单

在这里插入图片描述
思路一:暴力法 没什么好说的,双重循环计算所有元素两两组合相减的结果,取最大。

public int maxProfit(int[] prices) {
    int maxProfit=0;
    for (int i = 0; i < prices.length - 1; i++) {
        for (int j = i+1; j < prices.length; j++) {
            maxProfit=Math.max(maxProfit,prices[j]-prices[i]);
        }
    }
    return maxProfit;
}

时间复杂度:O(n^2)

思路二:优化暴力法到一次遍历 买卖股票从第二天开始我们每天都会"后悔":后悔没有在之前的最低点进行买入,只有这样我们的收益才会最大化。

由此可见,我们想要当天利益最大化,只需要在过去的某个最低点买入股票就好。所以我们只需要记录曾经出现过的最低点就好。

public int maxProfit(int[] prices) {
    int minPoint=Integer.MAX_VALUE,maxProfit=0;
    for (int i = 0; i < prices.length; i++) {
        //记录曾出现过最低点
        minPoint=Math.min(prices[i],minPoint);
        //当日-曾经的最低
        maxProfit=Math.max(maxProfit,prices[i]-minPoint);
    }
    return maxProfit;
}

时间复杂度:O(n)

从NO.121题不难看出:买股票的最佳时机是曾经!股市有风险,入股需谨慎!(狗头)

单纯的解答本题是比较简单的,但是买卖股票可以算作是一个系列的经典问题,在leetcode上就有本题一系列的变种问题:买卖股票的最佳时机、买卖股票的最佳时机II、买卖股票的最佳时机III、买卖股票的最佳时机IV、买卖股票的最佳时机含冷冻期、买卖股票的最佳时机含手续费。

虽然这些题有难有易,但是既然是一类问题,就有这一些通用的方法。

这六个问题都是由第四个问题简化变种而来的,第四题相较于本题多了一个参数k,限制只能进行k次交易;第一题也就是本题是只进行一次交易,相当于 k = 1;第二题是不限交易次数,相当于 k = +infinity(正无穷);第三题是只进行 2 次交易,相当于 k = 2;剩下两道也是不限次数,但是加了交易「冷冻期」和「手续费」的额外条件,其实就是第二题的变种。

之后记一下笔记总结一下这几个问题。

目前找到了"labuladong"大佬的公众号写的一篇《一个方法团灭 6 道股票问题》题解,打算先学习在总结。


本人菜鸟,有错误请告知,感激不尽!

更多题解和学习记录博客:博客github

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值