Bootstrap

LeetCode高频题:最少经过几次操作可以使数组变为非降序状态

LeetCode高频题:最少经过几次操作可以使数组变为非降序状态

提示:本题是系列LeetCode的150道高频题,你未来遇到的互联网大厂的笔试和面试考题,基本都是从这上面改编而来的题目
互联网大厂们在公司养了一大批ACM竞赛的大佬们,吃完饭就是设计考题,然后去考应聘人员,你要做的就是学基础树结构与算法,然后打通任督二脉,以应对波云诡谲的大厂笔试面试题!
你要是不扎实学习数据结构与算法,好好动手手撕代码,锻炼解题能力,你可能会在笔试面试过程中,连题目都看不懂!比如华为,字节啥的,足够让你读不懂题
在这里插入图片描述
基础知识:
【1】LeetCode高频题300. 最长递增子序列


题目

作者:牛客40502855号
来源:牛客网

给定一个大小为N的无序数组arr,对数组中的每个元素可进行如下操作:
将元素移动至数组的头部
将元素移动至数组的尾部
注意:这里的移动不是通过元素的交换完成的,而是直接将元素移动到指定位置,空出来的位置由别的元素顺次填满。
问:最少经过几次操作可以使数组变为非降序状态。


一、审题

输入:
第一行输入一个正数n代表数组arr的元素个数
第二行,给出n个正整数ai,代表数组中的元素
1<=n<=3*10的5次方
1<=ai<=10的9次方

输出:
一个数ans,代表将数组操作为非降序状态所需的最小次数

比如
arr=19 7 8 25
首先8移动到首部
arr=8 19 7 25
然后将7移动到首部
7 8 19 25

2次就搞定了


了解一下最长递增子序列

19 7 8 25
最长的非递减子序列长度是3
N总长度=4
N-3=1
实际上却需要移动2次,怎么解释呢?

要不整一个范围上的尝试???
设f(i)是将i位置元素调整之后,所需要的最少操作次数

主函数调用f(0位置开始),直到i到N位置越界,看看最少调整次数是?

那么我们来讨论一波,这个f怎么写才好

(0)当i=N越界时,不需要调动,操作0次,返回0次即可
(1)任意位置i时:可以让i位置不动,算操作0次,看看f(i+1)
(2)任意位置i时:可以让i位置去首部,算操作1次,剩下的arr,看看f(i+1)
(3)任意位置i时:可以让i位置去尾部,算操作1次,剩下的arr,看看f(i+1)
这三者的答案,选择最小值返回

中间动过的arr,需要我们带着玩???
这么做可以,但是这是极其不好的尝试办法,因为arr一直变动,而且不是简单的变量类型,这种方法,蠢!

再想新的办法

据说先排序:

需要一个大小为n的数组记录排好序的结果,
然后挨个比较,统计排序数组对应原数组的下标数组,它的最长递增子序列,
最后用n减去这个序列长度

在这里插入图片描述

比如上面的19 7 8 25
7 8 19 25这个排序数组对应原数组的索引下标数组为
1 2 0 3,找到1203最长连续递增子数组即可
——不连续的还不行【因此我们说的基础知识【1】还不能用】

之前求过最长递增子序列的长度——这里是不连续的哦!!!【是一个贼难的题目】
【1】LeetCode高频题300. 最长递增子序列

1 2 或者0 3 构成最长连续的递增子序列,构成的2长度
故,原数组需要动几次呢?4-2=2次

没错,就是这样干

这是啥原理呢???

你最终就是想要把arr变为非递减状态,

那么原数组排序后,位置不动的最长连续递增那一堆,是我们不需要移动的位置,你想想是不是
19 7 8 25
排序后,是7 8 19 25
78 19 是动过得,但是因为19 移动到右边,然后25移动到右边就自动OK了,那么7 8是我们不需要移动的数
因此动就是要动4-2的长度

这,很难想到

OK,方法是很难想到,蔚来考的笔试题,现场3个题都很难,因此没人做出来
只能线下慢慢想,见过可能下次就会了……

我们谢谢代码!

给你一个数组arr,最长连续递增子序列的长度怎么求?

1 2 0 3
最长连续递增,既然是连续的

那就考虑以i结尾的子数组,它最长连续递增长度是多少?
实际上就是填一个表dp[i]表示以i结尾的子数组,长度是多少?
在这里插入图片描述
对于dp[i],只要是[i]>[i-1],dp[i]=do[i-1]+1,否则就是1

中途将最大值更新给max即可

手撕代码:

    //实际上就是填一个表dp[i]表示以i结尾的子数组,长度是多少?
    public static int longestConsecutiveArrLen(int[] arr){
        if (arr == null || arr.length == 0) return 0;
        if (arr.length == 1) return 1;

        int N = arr.length;
        int max = 1;//至少1
        int[] dp = new int[N];//dp[i]表示以i结尾的子数组,长度是多少
        //先都是1
        for (int i = 0; i < N; i++) {
            dp[i] = 1;//最次就是自己
        }
        for (int i = 1; i < N; i++) {
            //从1位置看,只要是严格大于前面的位置,就算OK
            dp[i] = arr[i] > arr[i - 1] ? dp[i -1] + 1 : 0;
            max = Math.max(max, dp[i]);
        }

        return max;
    }

    public static void test(){
        int[] arr = {1,2,0,3};
        System.out.println(longestConsecutiveArrLen(arr));
    }

    public static void main(String[] args) {
        test();
    }

测试:2
问题不大,这只要是连续的就行了好说

然后我们来破解原题!

输入:
第一行输入一个正数n,代表数组arr的元素个数
第二行,给出n个正整数ai,代表数组中的元素

输出:
一个数ans,代表将数组操作为非降序状态所需的最小次数

原始数组arr,排序后为arr2
还需要用数组map记录排序后arr2对应到原始数组arr的下标位置

我们排序之后还要知道下标,就需要打包一个节点,把读进来的arri和下标i放一个节点
还要准备比较器,用arri的val排序,升序

        //加装下标
        public static class Node{
            public int val;
            public int index;//下标

            public Node(int v, int i){
                val = v;
                index = i;
            }
        }

        //比较器
        public static class cptr implements Comparator<Node> {
            @Override
            public int compare(Node o1, Node o2){
                return o1.val - o2.val;//升序
            }
        }

okay,正式手撕本题的代码:

public static void main(String[] args) {
            Scanner in = new Scanner(System.in);
            // 注意 hasNext 和 hasNextLine 的区别
            int N = in.nextInt();
            Node[] arr = new Node[N];
            for (int i = 0; i < N; i++) {
                int val = in.nextInt();
                arr[i] = new Node(val, i);//包装好放入arr
            }
            Arrays.sort(arr, new cptr());//排序
            //整一个节点装下标吧
            int[] ids = new int[N];
            for (int i = 0; i < N; i++) {
                ids[i] = arr[i].index;//把位置搞出来,然后查最长递增子数组的长度
            }
            int max = longestConsecutiveArrLen(ids);
            int ans = N - max;

            System.out.println(ans);
        }

无法就是读取arri,把arri和i包装为node
然后排序node
然后把下标ids读出来
用我们上面准备好的最长严格递增子序列的求解函数,把ids的最长递增长度求出来
然后N-max就是结果了

测试:

6
6 5 4 3 2 1
5

4
19 7 8 25
2

问题不大

整体时间复杂度在排序那o(nlog(n))


总结

提示:重要经验:

1)本题难就难在怎么想解法,你要将其变为非递减状态,原来那个数组排序后位置动过的最长递增那个子数组,是不需要动的,它的长度就是不动的长度
2)N-这个最长严格递增子序列长度就是我们要动的其他位置,难想但是见过,下次就明白了
3)另外,我们不求这个题,单独求最长递增子序列长度怎么求,那个题很经典,也很困难,要学学
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

;