两数相除
题目链接:两数相除
个人版本一
class Solution {
public int divide(int dividend, int divisor) {
return divideThem(dividend, divisor, 0);
}
public int divideThem(int dividend, int divisor, int k){
if(divisor == 1) return dividend;
if(dividend == Integer.MIN_VALUE){
if(divisor == -1) return Integer.MAX_VALUE;
if(divisor == Integer.MIN_VALUE) return 1;
k = 1;
}
if(divisor == -1) return ~dividend+1;
if(divisor == Integer.MIN_VALUE) return 0;
int den = dividend == Integer.MIN_VALUE?Integer.MAX_VALUE:(~dividend)+1;
int dor = (~divisor)+1;
if(dividend > 0 && divisor < 0) return (~divideThem(dividend, dor, k))+1;
if(dividend < 0 && divisor > 0) return (~divideThem(den, divisor, k))+1;
if(dividend < 0 && divisor < 0) return divideThem(den, dor, k);
int count = 0;
while (dividend == Integer.MAX_VALUE || (dividend+k) >= divisor){
dividend = dividend - divisor;
count++;
}
return count;
}
}
个人版本二(版本一改进)
class Solution {
public int divide(int dividend, int divisor) {
if(dividend == 0) return 0;
if(divisor == 1) return dividend;
if(divisor == -1){
if(dividend>Integer.MIN_VALUE) return -dividend;// 只要不是最小的那个整数,都是直接返回相反数就好啦
return Integer.MAX_VALUE;// 是最小的那个,那就返回最大的整数啦
}
long a = dividend;
long b = divisor;
int sign = 1;
if((a>0&&b<0) || (a<0&&b>0)){
sign = -1;
}
a = a>0?a:-a;
b = b>0?b:-b;
int res = div(a,b);
return sign>0?res:-res;
}
/**
e.g. 10 除 3
整体过程不断叠加3和count,首先判断3+3是否大于10,不大于时除数变为6,继续叠,6+6,不符合,10-6=4是剩余的数目
继续执行该函数,重复流程
*/
public int div(long a, long b){ //
if(a<b) return 0;
int count = 1;
long tb = b; // 在后面的代码中不更新b
while((tb+tb)<=a){
count = count + count; // 最小解翻倍
tb = tb+tb; // 当前测试的值也翻倍
}
return count + div(a-tb,b);
}
};
官方版本一(二分法)
class Solution {
/**
* 使用二分查找的理论前提是,将输入数都转为负数统一处理,因为32位负数最小是-2^31,如果都转成正数,
* 正数最大是2^31-1,将-2^31转相反数就会溢出
* 假设z是除出来的上,x是被除数,y是除数,当x和y都是负数时有 z*y>=x>(z+1)*y,根据该方程可以使用二分查找,
* 查找最大的z使得z*y>=x
*/
public int divide(int dividend, int divisor) {
// 考虑被除数为最小值的情况
if (dividend == Integer.MIN_VALUE) {
if (divisor == 1) {
return Integer.MIN_VALUE;
}
if (divisor == -1) {
return Integer.MAX_VALUE;
}
}
// 考虑除数为最小值的情况
if (divisor == Integer.MIN_VALUE) {
return dividend == Integer.MIN_VALUE ? 1 : 0;
}
// 考虑被除数为 0 的情况
if (dividend == 0) {
return 0;
}
// 将所有的正数取相反数,这样就只需要考虑一种情况
// 将输入的数都转成负数,因为32位负数最小是-2^31,如果都转成正数,正数最大是2^31-,将-2^31转相反数就会溢出
boolean rev = false;
if (dividend > 0) {
dividend = -dividend;
rev = !rev;
}
if (divisor > 0) {
divisor = -divisor;
rev = !rev;
}
// left=0也是可以的,但是left=0就会因为下界变小而导致运算步骤冗余
// 因为mid是用来探寻商的,并且是正整数,也就是mid最小就是1
int left = 1, right = Integer.MAX_VALUE, ans = 0;
while (left <= right) {
// 注意溢出,并且不能使用除法
int mid = left + ((right - left) >> 1);
boolean check = quickAdd(divisor, mid, dividend);
if (check) {
ans = mid;
// 注意溢出
if (mid == Integer.MAX_VALUE) {
break;
}
left = mid + 1;
} else {
right = mid - 1;
}
}
return rev ? -ans : ans;
}
// 快速乘
// 就是除数别说叠加的过程:2 4 8 16
/**
* add叠加进度和z除2进度是一样的,例如8除以2,当z=4,那么当z等于1时,add就会-8(输入数已经转成负数),所以如果z<=实际的商
* 当z=1时,add是大于x(转成负数后的被除数)的,那么就会返回true
*/
public boolean quickAdd(int y, int z, int x) {
// x 和 y 是负数,z 是正数
// 需要判断 z * y >= x 是否成立
int result = 0, add = y;
while (z != 0) {
// 这里表示z是奇数的时候为true,奇偶判断主要是根据字节组成末位判断,末位是2^0,其他都是2的倍数,也就是2^i,所以当末尾是1那么就是奇数
// 当末位是0就是偶数,z&1就是取出末位,对比是0还是1判断奇偶
/**
* 这部分逻辑主要是处理z是奇数问题,因为如果z是偶数,那么直接除以2不会有精度缺失,但是奇数除以2就会产生缺失,例如3/2,结果
* 取整是1。因为z除2的程度跟add倍增的程度是相等的,所以如果z是奇数,那么就会跟add倍增的冲突,结果是z因为精度缺失提前结束了,
* 从而得到比商更大的结果,例如7和3,如果没有该部分逻辑就是3,因为当z=3时除以2等于1,事实上缺失了1,还差一轮判断,导致3返回了,因而该段逻辑
* 就是补全缺失的部分的判断
* 偶数是不用考虑该部分,因为偶数每次除以2不会有缺失
*/
if ((z & 1) != 0) {
// 需要保证 result + add >= x
// 因为两个负数相加,可能会超出最小值,所以变成相减的模式:两个负数相减,范围是[-2^31+1, 2^31-1]
if (result < x - add) {
return false;
}
result += add;
}
if (z != 1) {
// 需要保证 add + add >= x
// 因为两个负数相加,可能会超出最小值,所以变成相减的模式:两个负数相减,范围是[-2^31+1, 2^31-1]
if (add < x - add) {
return false;
}
add += add;
}
// 不能使用除法
z >>= 1;
}
return true;
}
}
官方版本二(类二分法)
class Solution {
public int divide(int dividend, int divisor) {
// 考虑被除数为最小值的情况
if (dividend == Integer.MIN_VALUE) {
if (divisor == 1) {
return Integer.MIN_VALUE;
}
if (divisor == -1) {
return Integer.MAX_VALUE;
}
}
// 考虑除数为最小值的情况
if (divisor == Integer.MIN_VALUE) {
return dividend == Integer.MIN_VALUE ? 1 : 0;
}
// 考虑被除数为 0 的情况
if (dividend == 0) {
return 0;
}
// 一般情况,使用类二分查找
// 将所有的正数取相反数,这样就只需要考虑一种情况
// 这里考虑全部转成负数,因为32位负整数最小是-2^31,如果统一转成正数,而32位正整数最大是2^31-1,那么就会溢出
boolean rev = false;
if (dividend > 0) {
dividend = -dividend;
rev = !rev;
}
if (divisor > 0) {
divisor = -divisor;
rev = !rev;
}
List<Integer> candidates = new ArrayList<Integer>();
candidates.add(divisor);
int index = 0;
// 不断寻找,直到找到最小的倍数,并且他的两倍不大于被除数,例如8除以3,最后一个符合条件的就是
// -6(因为8和3都被转为了负数),-6的两倍不大于-8,所以该数组最后一个就是-6
// 找到最后一个符合条件的数,那么该数是能尽量减少计算规模的,例如8除以3,-6肯定大于-8,
// 所以-8直接减去-6等于-2,这样就减少了一大部分,而不是每次从-3开始减,当被除数比较大就会产生多次操作
while (candidates.get(index) >= dividend - candidates.get(index)) {
candidates.add(candidates.get(index) + candidates.get(index));
++index;
}
int ans = 0;
// 从最后一个开始,也就是最接近被除数的开始,可以大大减少计算过程
for (int i = candidates.size() - 1; i >= 0; --i) {
if (candidates.get(i) >= dividend) {
// 因为candidates数组中每一元素都是除数的2^i次倍,例如下标为2,就是2^2,如果该下标的数
// 大于等于被除数,那么就能在被除数中将其去除,这个时候 1<<i,就是表示2^i次方,例如下标为1
// 经过该步骤就是2,表示减了两个除数,把这部分去除,那么就相当于商至少是2,接下来就继续,用省下来的数继续操作
ans += 1 << i;
dividend -= candidates.get(i);
}
}
return rev ? -ans : ans;
}
}
下一个排列
题目链接:下一个排列
个人版本一
// 题目意思是,例如1,2,3,找到他的下一个序列,1,2,3按顺序组成数字就是123,找到比123大
// 并且是最接近的,那就是132,所以答案就是1,3,2
class Solution {
public void nextPermutation(int[] nums) {
if(nums.length == 1) return;
int i = 1, len = nums.length, j, tmp;
j = nums[0];
// 递减情况处理
while (j>=nums[i]){
j = nums[i];
i++;
if(i == len) break;
}
// 是递减
if(i == len){
i = 0;
int border = (len-1)/2;
int index;
while (i <= border){
index = len-1-i;
swap(nums, i, index);
i++;
}
return;
}
// 其他情况
j = len-1; i = j-1;
// 从前往后扫,遇到前边的小于后边的就是符合的情况的,例如1,1,5,从后开开始
// 后边两个前边的1比5要小,那么就是符合情况,直接交换:1,5,1
while (i >= 0 && nums[i] >= nums[j]){
tmp = j-1;
// 当前边的比后边的大还需要进一步判断,例如:3,4,2
// 4比2大,3也比2大,但是4比3大,这里就可以交换4和3变成,3,4,2,
while (tmp > i && nums[tmp] <= nums[i]){
tmp--;
}
if(tmp > i){
j = tmp;
break;
}
i--;
}
swap(nums, i, j);
// 上述例子3,4,2并不是答案,只是确定了高位的值,但是肯定比nums数组组成的数要大,这里按升序排序就是将低位转成最小的
Arrays.sort(nums, i+1, len);
}
public void swap(int[] arr, int i, int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
官方版本一(个人版本一改进版)
class Solution {
public void nextPermutation(int[] nums) {
int i = nums.length - 2;
// 找到nums[i] < nums[i+1]的位置,这个时候[i+1, nums.len)是降序
// 在降序序列中不存在交换两个数得到下一个序列,因为下一个序列肯定比当前的要大
// 下一个序列是比当前的要大,这里找到nums[i],其实这是数组的高位,并且是最小的可变的高位
// 意思是,例如101,这里最小的可变的高位就是十位,最后的1和0互换,110就是下一个序列
// 将高位换成较大的数,就完成高位的改变
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
if (i >= 0) {
int j = nums.length - 1;
// 找到nums[i] < nums[j]
// 这里就是在[n+1, len)中找到比上述高位略大的数,然后进行交换,交换后仍然是教降序序列
// 为什么将可变的高位改变成略大的数呢,因为下一个序列是比当前数大的,高位的改变以后肯定就比原本的
// 数要大,但是并不是最接近原来序列的数
while (j >= 0 && nums[i] >= nums[j]) {
j--;
}
// 交换nums[i]和nums[j]
swap(nums, i, j);
}
// 当高位改变完成后,高位以后的数字,只要是变成最小的,那么就是下一个序列了
reverse(nums, i + 1);
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public void reverse(int[] nums, int start) {
int left = start, right = nums.length - 1;
while (left < right) {
swap(nums, left, right);
left++;
right--;
}
}
}
最长有效括号
题目链接:最长有效括号
个人版本一
class Solution {
public int left = -1;
public int right = -1;
public int longestValidParentheses(String s) {
int len = s.length();
Stack<int[]> stack = new Stack<>();
if(len == 0||len ==1){
return 0;
}
int start = 0, i;
int[] indexNums;
// 小于len-1是因为start在最末尾,那是不可能匹配的
while (start < len-1){
for(i=start ; i<len-1 ; i++){
if(s.charAt(i) == '('&&s.charAt(i+1) == ')'){
left = i;right = i+1;
break;
}
}
// 后续没有完整的括号了就结束
if(i == len-1) break;
getLongest(s, left, right);
// 当栈中存在元素
while (!stack.isEmpty()){
indexNums = stack.peek();
// 是连续的
if(indexNums[1] == left-1){
left = indexNums[0];
// 这里弹出又添加是为了下一次黏连准备
stack.pop();
// 主要是为了在处理完黏连的括号后,黏连括号整体外层还存在套壳的情况,例如处理完()(()) 但是他外层还有一层括号
// 也就是(()(()))
getLongest(s, left, right);
// 当到头来就退出
if(right == len-1 && left == 0){
break;
}
}else{
break;
}
}
// flag用于处理黏连阶段已经添加问题
stack.push(new int[]{left,right});
// 更新初始位置
start = right+1;
}
// 为空说明没有
if(stack.isEmpty()){
return 0;
}
int maxLen = 2;
// 拿到最长
while (!stack.isEmpty()){
indexNums = stack.pop();
int tmpLen = indexNums[1]-indexNums[0]+1;
if(maxLen < tmpLen){
maxLen = tmpLen;
}
}
return maxLen;
}
public void getLongest(String s, int i, int j){
int len = s.length();
// 保证不溢出
if(i >= 0 && j < len){
// 处理左边多一个(,右边多一个)
if(i-1 >=0 && j+1 < len && s.charAt(i-1) == '('&&s.charAt(j+1) == ')'){
// 表示更更大范围匹配的括号
if(j+1 > right){
left = i-1;
right = j+1;
}
getLongest(s, i-1, j+1);
}
// 处理右边多一个括号
if(j+2 < len && s.charAt(j+1) == '(' && s.charAt(j+2) == ')'){
if(j+2 > right){
right = j+2;
}
getLongest(s, i, j+2);
}
}
}
}
官方版本一(动态规划)
/**
*我们定义 ]dp[i] 表示以下标 ii 字符结尾的最长有效括号的长度。我们将 dp 数组全部初始化为 0 。显然有效的子串一定以 ‘)’ 结尾,因此我们可以知道以 ‘(’ 结尾的子串对应的 dp 值必定为 00 ,我们只需要求解 ‘)’ 在 dp 数组中对应位置的值。
*
* 我们从前往后遍历字符串求解 \textit{dp}dp 值,我们每两个字符检查一次:
*
* s[i]=‘)’ 且 s[i−1]=‘(’,也就是字符串形如 “……()”“……()”,我们可以推出:
*
* dp[i]=dp[i−2]+2
*
* 我们可以进行这样的转移,是因为结束部分的 "()" 是一个有效子字符串,并且将之前有效子字符串的长度增加了 22
*
* s[i]=‘)’ 且 s[i−1]=‘)’,也就是字符串形如 “……))”“……))”,我们可以推出:
* 如果 s[i−dp[i−1]−1]=‘(’,那么
*
* dp[i]=dp[i−1]+dp[i−dp[i−1]−2]+2
*
*/
class Solution {
// 上述总体而言就是检查两种情况,现有匹配情况下右边多一对()和外层再套一层()
public int longestValidParentheses(String s) {
int maxans = 0;
int[] dp = new int[s.length()];
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) == ')') {
if (s.charAt(i - 1) == '(') {
// 处理右边多一对()
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
} else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
// 处理外边套一层()
dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
}
maxans = Math.max(maxans, dp[i]);
}
}
return maxans;
}
}
官方版本二(栈匹配)
class Solution {
// 模拟匹配的过程,进行查找最长有效括号
// 算法主要是利用栈进行存储匹配,当匹配的元素的下标不会出现在栈中,而在栈底是最后一个没有被匹配的右括号的下标
// 如果栈不为空,当前右括号的下标减去栈顶元素即为以该右括号为结尾的最长有效括号的长度
// 例如()(),这个整个都是匹配的,栈底开始是-1,按上边描述就是匹配以后会进行退栈,最终导下标3的时候,栈中只有-1
// 3-(-1)=4,就是他的长度
// 如())()(),下标为2的位置就不符合,当下标为2的元素后边都匹配完,下标来6,也就是末尾的),那么就用6-2=4
// 比较得最长就是4
// 这是利用括号匹配的一个连续性进行实现的
public int longestValidParentheses(String s) {
int maxans = 0;
Deque<Integer> stack = new LinkedList<Integer>();
// 首先放个-1,
stack.push(-1);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
stack.push(i);
} else {
// 弹出匹配的位置,空出前一个不相等的位置,那么在后续的到达末尾或者是下一个不匹配的右括号的时候
// 减去这个位置就能拿到中间匹配的长度
stack.pop();
// 栈为空说明当前的右括号为没有被匹配的右括号
if (stack.isEmpty()) {
stack.push(i);
} else {
// 这里进行比较就拿到最长的
maxans = Math.max(maxans, i - stack.peek());
}
}
}
return maxans;
}
}
官方版本三
// 利用括号匹配规则就是从左到右,如果是有效的括号组合,那么就是左括号的数量>=右括号的数量
class Solution {
public int longestValidParentheses(String s) {
// 从左往右扫,统计数量,但是这里不能处理左括号数量大于右括号数量的情况,例如(()
int left = 0, right = 0, maxlength = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * right);
} else if (right > left) {
left = right = 0;
}
}
// 这里反过来进行处理左括号大于右括号的情况,判断跟上边相反
left = right = 0;
for (int i = s.length() - 1; i >= 0; i--) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * left);
} else if (left > right) {
left = right = 0;
}
}
return maxlength;
}
}
搜索旋转排序数组
题目链接:搜索旋转排序数组
个人版本一(二分查找)
class Solution {
public int search(int[] nums, int target) {
int len = nums.length, left = 0, right = len, mid, index = -1, tail = nums[len-1];
boolean isLeft = false;
// 通过初始对比,判断目标是在左边部分还是右边部分
// 左边部分也就是旋转过去你,恒大于右边部分
if(target > tail){
isLeft = true;
}else if(target < tail){
isLeft = false;
}else{
return len-1;
}
while (left < right){
mid = (left+right)/2;
// 根据在左边还是右边,存在不同特性
// 在左边大的部分
if(isLeft){
// 在左边部分,比目标数大那当前的数就不可能是在右边部分
if(nums[mid] > target){
right = mid;
}else if(nums[mid] < target){
// 比目标数要小,有可能是右边部分,但是我们只需要在左边部分搜索
if(nums[mid] <= tail){
right = mid;
}else{
left = mid+1;
}
}else{
index = mid;
break;
}
}else{
// 7,8,1,2,3,4,5,6
// 在右边小的部分
if(nums[mid] > target){
// 比目标数大的有可能是左边部分
if(nums[mid] <= tail){
right = mid;
}else{
left = mid+1;
}
}else if(nums[mid] < target){
// 目标数在右边部分,那么比目标数小的,就不可能在左边部分
left = mid+1;
}else{
index = mid;
break;
}
}
}
return index;
}
}
在排序数组中查找元素的第一个和最后一个位置
个人版本一(二分查找)
class Solution {
public int[] searchRange(int[] nums, int target) {
int len = nums.length;
int first = -1, second = -1;
if(len == 0){
return new int[]{-1, -1};
}
int left = 0, right = len-1, mid;
while (left <= right){
mid = (left+right)/2;
if(nums[mid] > target){
right = mid-1;
}else if(nums[mid] < target){
left = mid+1;
}else {
int i = mid;
while (i > 0 && nums[i-1] == target){
i--;
}
first = i;
i = mid;
while (i < len-1 && nums[i+1] == target){
i++;
}
second = i;
break;
}
}
return new int[]{first, second};
}
}
搜索插入位置
题目链接:搜索插入位置
个人版本一
class Solution {
public int searchInsert(int[] nums, int target) {
int len = nums.length, index = 0, left = 0, right = len - 1, mid;
if (nums[right] < target) {
return right+1;
}
if(nums[left] >= target){
return left;
}
while (left <= right) {
mid = (left + right) / 2;
if(nums[mid] > target){
right = mid-1;
}else if(nums[mid] < target){
left = mid+1;
}else{
index = mid;
break;
}
}
index = left > right? nums[left] > nums[right]? left:right : index;
return index;
}
}
有效数独
题目链接:有效数独
个人版本一
class Solution {
public boolean isValidSudoku(char[][] board) {
// 键组成是:行加数
Map<Character, List<String>> table = new HashMap<>();
for(int i=0 ; i < 9 ; i++){
for(int j = 0 ; j < 9 ; j++){
// 空就跳过
if(board[i][j] == '.') continue;
List<String> l = table.get(board[i][j]);
if(l != null){
// table中list装的是每个数组的位置的组合: 12,表示1行1列
// 从后向前遍历,只有后边三个才可能符合在一个小的方格内的,往前的就不符合了
int count = 3;
for(int k=l.size()-1 ; k>=0 ; k--){
String zuo = l.get(k);
int r = Character.getNumericValue(zuo.charAt(0));
int c = Character.getNumericValue(zuo.charAt(1));
if(i == r || j == c){
return false;
}
if(count > 0){
// 同一部分的
if(isSamePart(r, c, i, j)){
return false;
};
// count计数减少,当计数为0的时候到达导数第三个就肯定不是同一块了
count--;
}
}
// 把新的添加回去
l.add(i+""+j);
}else{
List<String> list = new ArrayList<>();
list.add(i+""+j);
table.put(board[i][j], list);
}
}
}
return true;
}
// 是否同一个九宫格
public boolean isSamePart(int r2, int c2, int r1, int c1){
boolean result = false;
if(r1/3 == r2/3 && c1/3 == c2/3){
result = true;
}
return result;
}
}
官方版本一(遍历)
class Solution {
public boolean isValidSudoku(char[][] board) {
int[][] rows = new int[9][9];
int[][] columns = new int[9][9];
int[][][] subboxes = new int[3][3][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char c = board[i][j];
if (c != '.') {
int index = c - '0' - 1;
rows[i][index]++;
columns[j][index]++;
subboxes[i / 3][j / 3][index]++;
if (rows[i][index] > 1 || columns[j][index] > 1 || subboxes[i / 3][j / 3][index] > 1) {
return false;
}
}
}
}
return true;
}
}
解数独
题目链接:解数独
个人版本一(回溯)
class Solution {
public int count = 0;
public void solveSudoku(char[][] board) {
int[][] rows = new int[9][10];
int[][] columns = new int[9][10];
int[][][] subboxes = new int[3][3][10];
int index;
// 初始化行列表
for(int i=0 ; i<9 ; i++){
for(int j=0 ; j<9 ; j++){
if(board[i][j] != '.'){
index = board[i][j] - '0';
rows[i][index]++;
columns[j][index]++;
subboxes[i / 3][j / 3][index]++;
count++;
}
}
}
getSudoku(board, 0, -1 ,rows, columns, subboxes);
}
public void getSudoku(char[][] board, int row, int col, int[][] rows, int[][] columns, int[][][] subboxes){
// 没有到最后
if(count < 81){
col++;
// 处理上次到末尾的情况
if(col == 9 && row < 8){
row++;
col = 0;
}
// 找下一个
while (board[row][col] != '.'){
col++;
if(col == 9 && row < 8){
row++;
col = 0;
}
}
List<Character> list = new ArrayList<>();
// 该位置候选的元素
getAlldiff(list, board, row, col ,rows, columns, subboxes);
for(int i=0 ; i<list.size() ; i++){
board[row][col] = list.get(i);
count++;
if(count < 81){
int index = list.get(i)- '0';
rows[row][index]++;
columns[col][index]++;
subboxes[row / 3][col / 3][index]++;
getSudoku(board, row, col ,rows, columns, subboxes);
if(count == 81) break;
count--;
rows[row][index]--;
columns[col][index]--;
subboxes[row / 3][col / 3][index]--;
board[row][col] = '.';
}
}
}
}
public void getAlldiff(List<Character> list, char[][] board, int row, int col, int[][] rows, int[][] columns, int[][][] subboxes){
int i=1, p1 = row/3, p2 = col/3;
while (i<=9){
if(rows[row][i] == 0 && columns[col][i] == 0 && subboxes[p1][p2][i] == 0 ){
list.add(Character.forDigit(i, 10));
}
i++;
}
}
}
官方版本一(回溯)
class Solution {
private boolean[][] line = new boolean[9][9];
private boolean[][] column = new boolean[9][9];
private boolean[][][] block = new boolean[3][3][9];
private boolean valid = false;
private List<int[]> spaces = new ArrayList<int[]>();
public void solveSudoku(char[][] board) {
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
spaces.add(new int[]{i, j});
} else {
int digit = board[i][j] - '0' - 1;
line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = true;
}
}
}
dfs(board, 0);
}
public void dfs(char[][] board, int pos) {
if (pos == spaces.size()) {
valid = true;
return;
}
int[] space = spaces.get(pos);
int i = space[0], j = space[1];
for (int digit = 0; digit < 9 && !valid; ++digit) {
if (!line[i][digit] && !column[j][digit] && !block[i / 3][j / 3][digit]) {
line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = true;
board[i][j] = (char) (digit + '0' + 1);
dfs(board, pos + 1);
line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = false;
}
}
}
}
官方版本二(位运算优化)
// 将版本一的数组的作用用一字节的表示
//
class Solution {
private int[] line = new int[9];
private int[] column = new int[9];
private int[][] block = new int[3][3];
private boolean valid = false;
private List<int[]> spaces = new ArrayList<int[]>();
public void solveSudoku(char[][] board) {
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
spaces.add(new int[]{i, j});
} else {
int digit = board[i][j] - '0' - 1;
// 初始化各位
flip(i, j, digit);
}
}
}
dfs(board, 0);
}
public void dfs(char[][] board, int pos) {
if (pos == spaces.size()) {
valid = true;
return;
}
int[] space = spaces.get(pos);
int i = space[0], j = space[1];
// 或运算,将同行同列同一个宫的元素都交出来,也就是该运算的字节表示中包含了这三个方面的数
// 取反后其中1表示可以添加的数,第i位为1就表示数i+1可以被填入
// 因为位中我们只需要9位,从左到右,表示数字1到9,实际存储的位不止9位,更高的位是0,
// 当我们取反后,高位就变成了1,但我们进行或运算以及取反的运算的目的是,最终的结果中
// 1的位置表示的是可以添加的数,所以需要将高位的1变成0,那么就可以与0x1ff相与
// 0x1ff就是111111111,事实上他实际是000......0111111111,前边的高位都是0.所以
// 能将最终要得到的位的高位变成0
int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
// mask &= (mask - 1)就是去除低位的1,表示添加了该数到九宫格中,
// 假设mask最低为是1,那么mask-1后就是0,相与是0,mask是0也是同样
for (; mask != 0 && !valid; mask &= (mask - 1)) {
// mask & (-mask)是拿到最低位的1
// 例如mask是,010000110,最低位的1就是导数第二个,-mask相当于:~mask+1
// ~mask就是取反:101111001,加1以后是101111010,二者相与就是000000010
// 最低位就是倒数第二个1
int digitMask = mask & (-mask);
// Integer.bitCount是统计位中1的个数
// 这里digit得到这个最低位的 1究竟是第几位
// 原理是,例如数字3,就是000000100表示,也就是数字=位数+1,现在1是第2位,那就表示数字3
// 减1以后,1对应的是第2位(最右边是第0位)的右边都会变成1上边减1后是000000011
// 通过统计1的个数就能拿到他是第几位,上边2个1,就是第2位
// 第2位是用来方便在位中的存储的
int digit = Integer.bitCount(digitMask - 1);
// 更新记录的信息,表示添加了这个数到九宫格
flip(i, j, digit);
// 添加数到九宫格
board[i][j] = (char) (digit + '0' + 1);
dfs(board, pos + 1);
// 更新记录的信息,表示在九宫格去掉了这个数,传进同样的数,异或后就是去掉原来的1,表示删除了
flip(i, j, digit);
}
}
public void flip(int i, int j, int digit) {
// 传进来的digit比实际数要少1
// 用位表示存在某个数
// 例如 digit是1,用位表示就是0000000001,1 << 表示左移一位,变成 00000000010,
// 初始line[i]是0,也就是0000000000,异或后是,00000000010,从左往右,最右边表示0位,导数第二是第
// 位,第一位就表示数字2(传进来的digit比实际数少1)包含了,即第i位表示i+1数包含也就是用位的关系去表示之前的数组
line[i] ^= (1 << digit);
column[j] ^= (1 << digit);
block[i / 3][j / 3] ^= (1 << digit);
}
}
官方版本三(枚举优化)
class Solution {
// 对官方版本二继续优化
// 其实就是优化了单数的情况
// 题目给出的九宫格每行每列的数是确定的,一行或者一列或者一个宫缺少一个数,那么可以确定整个数是什么,而不需要继续递归
private int[] line = new int[9];
private int[] column = new int[9];
private int[][] block = new int[3][3];
private boolean valid = false;
private List<int[]> spaces = new ArrayList<int[]>();
public void solveSudoku(char[][] board) {
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] != '.') {
int digit = board[i][j] - '0' - 1;
flip(i, j, digit);
}
}
}
while (true) {
boolean modified = false;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
// 处理单数情况,mask & (mask - 1)表示去掉低位的1,因为是单数,所以只有一个1
// 那么去掉了就是0
if ((mask & (mask - 1)) == 0) {
int digit = Integer.bitCount(mask - 1);
flip(i, j, digit);
board[i][j] = (char) (digit + '0' + 1);
modified = true;
}
}
}
}
if (!modified) {
break;
}
}
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
spaces.add(new int[]{i, j});
}
}
}
dfs(board, 0);
}
public void dfs(char[][] board, int pos) {
if (pos == spaces.size()) {
valid = true;
return;
}
int[] space = spaces.get(pos);
int i = space[0], j = space[1];
int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
for (; mask != 0 && !valid; mask &= (mask - 1)) {
int digitMask = mask & (-mask);
int digit = Integer.bitCount(digitMask - 1);
flip(i, j, digit);
board[i][j] = (char) (digit + '0' + 1);
dfs(board, pos + 1);
flip(i, j, digit);
}
}
public void flip(int i, int j, int digit) {
line[i] ^= (1 << digit);
column[j] ^= (1 << digit);
block[i / 3][j / 3] ^= (1 << digit);
}
}