变为棋盘
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);