【学习】大厂经典算法题整理(一)

前言

整个一月学习状态其实挺好的,结果临近过年节奏打乱了....之前的很多计划都没有如期实施,不爽...这篇算是年后第一篇整理性质的文章,也算是找找状态。

最近看了左程云老师的算法课,觉得讲的通透也高效,恰逢其举了几个经典例题,故在此记录,方便后续复习。

小红书

题目描述:

摸着石头过河
 * [0,4,7] 0 表示这里石头没有颜色,变红的代价是4,变蓝的代价是7.
 * [1,X,X] 1 表示这里石头已经是红,而且不能改颜色,所以后两个数X无意义
 * [2,X,X] 2 表示这里石头已经是蓝,而且不能改颜色,所以后两个数X无意义
 * 颜色只可能是0、1、2,代价一定>=0
 * 给你一批这样的数组,要求最后必须所有石头都有颜色,且红色和蓝色一样多,返回最小代价
 * 如果怎么都无法做到,返回-1

解题思路:

首先找到边界条件:
如果石头总数为奇数,则无论如何都无法做到
如果给定带颜色石头的单一颜色数量超过总数n的一半,则无论如何都无法做到

之后,可以依照贪心的思路,将没有颜色的石头都刷成一种颜色,之后再从这些石头中,找出变色代价最小的几个石头做变色,这里【变色代价】其实就是红色代价减去蓝色代价(默认刷红色,反之亦然),如[0,4,7],若将该石头先刷为红色,则当前代价为4,将其改为蓝色后的新代价为4-(4-7).

那么如何找到最小的几个【变色代价】呢,直接按照【变色代价】进行排序,再从低到高取石头进行变色即可

实例代码:

public class Code01_xiaohongshu {
    public static int minCost(int[][] stones){
        if (stones == null || stones.length < 2){
            // 为空或者只有一个,做不到
            return -1;
        }
        int n = stones.length;
        if (n%2 != 0){
            // 如果是奇数,也不行
            return -1;
        }
        int redStoneNums = 0; // 蓝色数量
        int blueStoneNums = 0; // 红色数量
        int cost = 0; // 总花销
        for (int[] temp : stones){
            // 统计每种颜色个数,并计算全为红色的代价
            if (temp[0] == 1){
                redStoneNums ++;
            } else if (temp[0] == 2){
                blueStoneNums ++;
            } else {
                cost += temp[1]; // 默认全给红色
            }
        }
        
        if (redStoneNums > n/2 || blueStoneNums > n/2){
            return -1; // 任意一种颜色大于总数的一半,则做不到
        }

        Arrays.sort(stones,(a,b) -> a[0]==0 && b[0]==0 ? b[1]-b[2]-a[1]+a[2] : a[0]-b[0]);
        // 以红色成本-蓝色成本的差(变色代价)作为排序标准,降序排列,
        // 因为默认将所有球归为红色,而降序后靠前的元素其更改为蓝色后的成本缩减幅度最大

        for (int i = 0 ; i < n/2-blueStoneNums ; i++){
			// 计算将这些石头变为蓝色后的代价
            cost -= stones[i][1]-stones[i][2];
        }
        return cost;
    }
}

网易

题目描述:

环形糖果
 * 给定一个正数数组arr,表示每个小朋友的得分
 * 任何两个相邻的小朋友如果得分一样,怎么分糖果无所谓
 * 但如果得分不一样
 * 分数大的一定要比分数少的多拿一些糖果
 * 假设所有的小朋友坐成一个环形,返回在不破坏上一条规则的情况下,需要的最少糖果数

解题思路:

这道题可以先不考虑环形
将题目变成:一排小朋友依照邻居的分数进行糖果的分配
那么,每一名小朋友的糖果仅需要与左右比较即可(可以暴力递归)
用[3,4,3,2,1]举例
先单向的从左向右考虑:
arr[0]位置的孩子左侧没有人,则其糖果数目规定为1
arr[1]>arr[0],糖果为arr[0]+1,2
arr[2]<arr[1],则给1
则得到一组根据左手边分数分配糖果的数组:left[1,2,1,1,1]
同理,再单向的从右向左考虑得到:right[1,4,3,2,1]
最后在每个位置上取max即为最终结果:[1,4,3,2,1]

引入环形后,需要考虑边界的两个孩子分数大小,此时可以将环形化为单向问题
从数组中找到一个极小值,即arr[j-1] > arr[j] < arr[j+1],将j作为排头即可

实例代码:


public class Code02_wangyi {
    public static int nextIndex(int i, int n) {
        return i == n - 1 ? 0 : (i + 1);
    }

    public static int lastIndex(int i, int n) {
        return i == 0 ? (n - 1) : (i - 1);
    }

    public static int minCandy(int[] arr){
        if (arr == null){
            return 0;
        }
        if (arr.length < 2){
            return 1;
        }
        int startLoc = 0;
        int n = arr.length;
        for (int i = 0 ; i < n ; i++){
            // 找到极小位置
            if (arr[i] <= arr[lastIndex(i,n)] && arr[i] <= arr[nextIndex(i,n)]){
                // i位置为局部最小,作为起始点
                startLoc = i;
                break;
            }
        }
        // 此时startLoc 为 起始点
        int[] nums = new int[n+1]; 
        // 之所以是n+1,是因为将排头也放在了末尾,方便n-1位置的孩子与n位置(0位置)进行比较
        for (int i = 0 ; i < n ; i++, startLoc = nextIndex(startLoc,n)){
            nums[i] = arr[startLoc]; // 调整顺序,得到新数组即可单向查找
        }

        int[] left = new int[n + 1];
        left[0] = 1;
        for (int i = 1; i <= n; i++) {
            left[i] = nums[i] > nums[i - 1] ? (left[i - 1] + 1) : 1;
            // 与左侧邻居比较
        }
        int[] right = new int[n + 1];
        right[n] = 1;
        for (int i = n - 1; i >= 0; i--) {
            right[i] = nums[i] > nums[i + 1] ? (right[i + 1] + 1) : 1;
            // 与右侧邻居比较
        }
        int ans = 0;
        for (int i = 0; i < n; i++) {
            ans += Math.max(left[i], right[i]);
            // 汇总取max
        }
        return ans;
    }
}

腾讯

题目描述:

坐船
 * 给定一个正数数组arr,代表每个人的体重。给定一个正数limit代表船的载重
 * 所有船都是同样的载重量,每个人的体重都一定不大于船的载重
 * 要求:
 * 1. 可以1个人单独一搜船
 * 2. 一艘船如果坐2人,两个人的体重相加需要是偶数,且总体重不能超过船的载重
 * 3. 一艘船最多坐2人
 * 返回如果想所有人同时坐船,船的最小数量

解题思路:

与上面的题类似,可以先不考虑奇偶问题,先考虑如何组合是最小的。

最小的可能一定是:能两人坐就两人坐,剩下的人再一人一船。(听着像废话哈~)

那么就需要先考虑哪些人能量量配队,且其是最优配队。
第一阶段:
可以将数组arr(递增的)划分为 arr[0..i] < limit/2 , arr[i+1..] > limit/2这两部分
此时左侧最大与右侧的最小先尝试能否上船,如果不行,在左侧区域向左找体重小的,直到能够上船
之后在右侧向右找体重大的,直到上不去船,此时右侧移动了几位就能够上船几组。
直到左侧或右侧没有人选后,开始第二个阶段。

第二阶段:
左侧剩下的人两两组合上船,因为左侧部分一定是小于limit/2的,那么两两组合一定小于limit,
之后右侧一人一船,即完成了所有人登船且船数量最小。

那么考虑进两人相加为偶数的化,只需要什么样的两两组合为偶数即可。
奇数+奇数,偶数+偶数
因此在进行上述两个阶段前,将arr数组奇偶分开,分别求最少船数再相加就行了。

实例代码:


public class Code01_tengxun {
    public static int minBoat(int[] arr, int limit) {
        if (arr == null || arr.length < 1){
            return 0;
        }
        Arrays.sort(arr);
        if (arr[arr.length-1] > limit){
            // 排序后,最重的人若是超过船的限制,则表示不行
            return 0;
        }
        int odd = 0; // 奇数数量
        int even = 0; // 偶数数量
        for (int num : arr) {
            if ((num & 1) == 0){
                // 偶数,因为偶数二进制低位一定为0
                even++;
            } else {
                odd++;
            }
        }

        int[] oddsNum = new int[odd];
        int[] evensNum = new int[even];
        for (int i = arr.length-1 ; i >= 0 ; i--){
            // 奇偶分成两个数组
            if ((arr[i]&1) == 0){
                evensNum[--even] = arr[i];
            } else {
                oddsNum[--odd] = arr[i];
            }
        }
        return getMinValue(oddsNum,limit) + getMinValue(evensNum,limit);
    }

    
    public static int getMinValue(int[] arr, int limit) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        int N = arr.length;
        if (arr[N - 1] > limit) {
            return -1;
        }
        int lessR = -1;
        for (int i = N - 1; i >= 0; i--) {
            // 找到分界点
            if (arr[i] <= (limit / 2)) {
                lessR = i;
                break;
            }
        }
        if (lessR == -1) {
            // 如果为-1则表明arr全小于limit/2
            return N;
        }
        // 声明两个指针负责遍历
        int L = lessR;
        int R = lessR + 1;
        int noUsed = 0;
        while (L >= 0) {
            // 当左侧没人时,退出循环
            int solved = 0;
            while (R < N && arr[L] + arr[R] <= limit) {
                // 计算能够左右组合上船的人数
                R++;
                solved++;
            }
            if (solved == 0) {
                // 上不去则左移
                noUsed++;
                L--;
            } else {
                L = Math.max(-1, L - solved);
            }
        }
        int all = lessR + 1;
        int used = all - noUsed;
        int moreUnsolved = (N - all) - used;
        return used + ((noUsed + 1) >> 1) + moreUnsolved;
    }
}

posted @ 2023-02-05 14:56  小拳头呀  阅读(39)  评论(0编辑  收藏  举报