LeetCode-第十四个星期

变为棋盘

LeetCode782题,给定两种交换方式,判断一个二维数组是否能变换为指定的格式。

一个 N x N的 board 仅由 0 和 1 组成 。每次移动,你能任意交换两列或是两行的位置。

输出将这个矩阵变为 “棋盘” 所需的最小移动次数。“棋盘” 是指任意一格的上下左右四个方向的值均与本身不同的矩阵。如果不存在可行的变换,输出 -1。

示例:
输入: board = [[0,1,1,0],[0,1,1,0],[1,0,0,1],[1,0,0,1]]
输出: 2
解释:
一种可行的变换方式如下,从左到右:

0110     1010     1010
0110 --> 1010 --> 0101
1001     0101     1010
1001     0101     0101

第一次移动交换了第一列和第二列。
第二次移动交换了第二行和第三行。


输入: board = [[0, 1], [1, 0]]
输出: 0
解释:
注意左上角的格值为0时也是合法的棋盘,如:

01
10

也是合法的棋盘.

输入: board = [[1, 0], [1, 0]]
输出: -1
解释:
任意的变换都不能使这个输入变为合法的棋盘。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/transform-to-chessboard
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路分析:

1.该题需要先检测是否能排列成棋盘,然后在进行排列
2.是否能排列成棋盘,需要分析各自的行和列是否满足特别的规则
3.首先是行检测,如果拿第一行作为基准,那么和第一行完全相同的行的个数,不能大于完全相反行的个数大于1,反之也是。并且对于行来说,如果出现一个行和第一行的某些数相同,或者某些数相反时,是不可能排列成棋盘的,直接返回即可。
4.其次是列的检测,列的检测和行类似,只有两种符合条件的列01010和10101,不符合的直接返回False。
5.如果可以转换为棋盘,则开始进行转换,转换次数为行的转换次数+列的转换次数
6.列和行都只有01010和10101两种形式,如果我们拿10101作为标准,判断不符合的该形式的个数,之后根据行列的奇偶性选择小的交换数。

   public int movesToChessboard(int[][] board) {
        // 检测是否可以变为棋盘
        if (check(board)) {
            // 取出第一行和第一列,检测最小交换次数
            int[] row = board[0];
            int[] col = new int[board.length];
            for (int i = 0; i < board.length; i++) {
                col[i] = board[i][0];
            }
            return find(row) + find(col);
        } else {
            return -1;
        }
    }

    /**
     * 检测两个数组是否完全相同
     *
     * @param a 待比较数组
     * @param b 待比较数组
     * @return 数组相同返回 true,否则返回 false
     */
    private boolean isSame(int[] a, int[] b) {
        for (int i = 0; i < a.length; i++) {
            if (a[i] != b[i]) {
                return false;
            }
        }
        return true;
    }

    /**
     * 检测两个数组是否完全相反
     *
     * @param a 待比较数组
     * @param b 待比较数组
     * @return 数组相反返回 true,否则返回 false
     */
    private boolean isOpposite(int[] a, int[] b) {
        for (int i = 0; i < a.length; i++) {
            if (a[i] == b[i]) {
                return false;
            }
        }
        return true;
    }

    /**
     * 检测board是否可以通过行列交换变成棋盘
     *
     * @param board 棋盘
     * @return 可以变成棋盘返回 true,否则返回 false
     */
    private boolean check(int[][] board) {
        // 检测行是否只有两种模式
        // 以第一行为基准,检测其余所有的行,这些行要么和第一行完全相同,要么和第一行完全相反,否则不可能变换成棋盘
        int[] first = board[0];
        int cntSame = 1;
        int cntOpposite = 0;
        for (int i = 1; i < board.length; i++) {
            if (isSame(first, board[i])) {
                //当前行与第一行完全一致cntSame+1
                cntSame++;
            } else if (isOpposite(first, board[i])) {
                //当前行与第一行完全不一致cntOpposite+1
                cntOpposite++;
            } else {
                //当前行与第一行有些一致、有些不一致,直接返回false,不能转换为棋盘
                return false;
            }
        }
        // 检测两种模式的数量分布是否正确
        // 行为偶数时 只有当前cntSame == cntOpposite才能转换
        // 行为奇数时 肯定会多一个完全相同或者不完全相同的行出来
        if (cntSame == cntOpposite || cntSame == cntOpposite + 1 || cntSame == cntOpposite - 1) {
            // 行只有两种模式,且分布正确,进行列检测,
            // 行只有两种模式的情况下列必然也只有两种模式,只检测列的两种模式数量分布是否正确,只用第一个数字代表不同的两种模式进行计数
            int cnt0 = 0;
            int cnt1 = 0;
            for (int i : first) {
                if (i == 0) {
                    cnt0++;
                } else {
                    cnt1++;
                }
            }
            // 检测第一行中 0 和 1 的数量(代表两种模式的数量)是否分布正确
            // 列与行相同
            if (cnt0 == cnt1 || cnt0 == cnt1 + 1 || cnt0 == cnt1 - 1) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * 检测数据需要最少交换多少次成为有序
     *
     * @param tmp 待检测数组
     * @return 数组达到有序需要的最小交换次数
     */
    private int find(int[] tmp) {
        // 只检测 10101010…… 情况的错位数
        int start = 1;
        int error = 0;
        for (int i : tmp) {
            // 统计有多少错位
            if (i != start) {
                error++;
            }
            start = 1 - start;
        }

        // 需要交换的次数是错位的一半,因为一次交换可以消除两个错位
        // 排列为有序有两种可能,一种是 10101010……,一种是 01010101……
        // 两种情况下计算的错位数相加等于行数,所以我们只需要计算一种
        if (tmp.length % 2 == 0) {
            // 如果行数是偶数,排列为 10101010…… 或 01010101…… 都是可能的
            // 取两种情况下错位数的最小值,tmp.length - error代表01010101形式的错误数
            return Math.min(tmp.length - error, error) >> 1;
        } else {
            // 如果行数是奇数,其实只可能排列成一种情况,这取决于 1 和 0 的数量
            // 1 比较多,必然只可能排成 10101010……,0 比较多,只能排成 01010101……
            // 不可能排列成的那种情况下计算出来的错位数是一个奇数,所以可以通过检测错位数是否为奇数来判断采取哪个情况
            if (error % 2 == 0) {
                return error >> 1;
            } else {
                return (tmp.length - error) >> 1;
            }
        }
    }

最接近的三数之和

LeetCode16题在给定的数组中,找到三个数使其和是最近目标数值的。
在这里插入图片描述
思路分析:

类似于三层循环嵌套查找,只不过将数组排序后,可以提前结束循环,找到合适的三个值。

 //排序
        Arrays.sort(nums);
        int result = nums[0] + nums[1] + nums[2];
        //for循环整个数组,以每个下标的数作为第一个数,内部采用双指针
        for(int i=0;i<nums.length-2;i++){
            int left = i+1;
            int right = nums.length - 1;
            while(left != right){
                int min = nums[i] + nums[left] + nums[left + 1];
                //如果当前循环中,最小的三个数的和已经大于了target,说明后面的值的和会相差的越来越大
                if(target < min){
                    //判断是否比上轮循环更接近target
                    if(Math.abs(result - target) > Math.abs(min - target))
                        result = min;
                    break;
                }
                //如果当前循环中,最大的三个数的和已经小于了target,说明后面的值的和会相差的越来越大
                int max = nums[i] + nums[right] + nums[right - 1];
                if(target > max){
                    if(Math.abs(result - target) > Math.abs(max - target))
                        result = max;
                    break;
                }
                int sum = nums[i] + nums[left] + nums[right];
                // 判断三数之和是否等于target
                if(sum == target)
                    return sum;
                if(Math.abs(sum - target) < Math.abs(result - target))
                    result = sum;
                if(sum > target){
                    //如果当前和大于target,则右指针移动
                    right--;
                    //优化
                    while(left != right && nums[right] == nums[right+1])
                        right--;
                }
                else{
                    //如果当前和小于target,则左指针移动
                    left++;
                    while(left != right && nums[left] == nums[left-1])
                        left++;
                }
            }
            while(i<nums.length-2 && nums[i] == nums[i+1])
                i++;
        }
        return result;

重构字符串

LeetCode767题,给定一个字符串,要求两相邻的字符不同。
在这里插入图片描述
思路分析:

找到出现次数最多的字符,将其插入下标为偶数的位置,之后再插入其余字符。

        int[] arr = new int[26];
        char[] chars = S.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            arr[chars[i] - 'a'] ++;
        }
        // 找出最多数量的字符在arr中的索引
        int index = 0;
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > arr[index]) {
                index = i;
            }
        }
        int maxNum = arr[index];
        //如果某个字符出现的次数已经大于整个字符一半的长度,则不可能满足条件
        if (S.length() % 2 == 0 && maxNum > S.length() / 2) {
            return "";
        }
        if (S.length() % 2 != 0 && maxNum > S.length() / 2 + 1) {
            return "";
        }
        // 可以输出的话,在偶数索引位置先设置最多数量的字符
        char[] newChar = new char[chars.length];
        int evenIndex = 0;
        while (arr[index] > 0) {
            newChar[evenIndex] = (char) (index + 'a');
            evenIndex += 2;
            arr[index]--;
        }
        // 在将其与字符添加到chars数组中
        int oddIndex = 1;
        for (int i = 0; i < arr.length; i++) {
            while (arr[i] > 0) {
                //如果偶数为没有添加完,则将其添加完
                if (evenIndex < newChar.length) {
                    newChar[evenIndex] = (char) (i + 'a');
                    evenIndex += 2;
                }else {
                    //之后在开始添加奇数位的值
                    newChar[oddIndex] = (char) (i + 'a');
                    oddIndex += 2;
                }
                arr[i]--;
            }
        }
        return new String(newChar);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值