数组
一.二分查找
1. 二分查找模板
建议mid = left + (right - left) >> 1
其中「二分」模板其实有三套,常用的是2,3两种
l < r
1.当check(mid) == true
调整的是 left = mid
时:计算 mid 的方式应该为:mid = left + right + 1>> 1
,当最终剩余两个数字时,mid=right,调整left= mid,可以缩小范围
例如:
long l = 0, r = 1000009;
while (l < r) {
long mid = l + r + 1 >> 1;
if (check(mid)) {
l = mid;
} else {
r = mid - 1;
}
}
2.当check(mid) == true
调整的是right = mid
时:计算 mid 的方式应该为:mid = left + right >> 1
,最终只剩两个数字时,mid = left,调整right = mid,可以缩小范围
对应carl左闭右开情况 程序员carl
例如:
long l = 0, r = 1000009;
while (l < r) {
long mid = l + r >> 1;
if (check(mid)) {
r = mid;
} else {
l = mid + 1;
}
}
l <= r
3.一般情况,对应carl左闭右闭情况
while(left <= right){
int mid = (left + right) >> 1;
if(nums[mid] == target){
return mid;
}else if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] > target){
right = mid - 1;
}
}
经验总结
1.根据循环退出条件(left<right或left<=right),判断最后一次比较时left和right指针的指向,从而决定返回值(返回left,right,left-1,left+1,right-1,right+1)
以left<= right 为例,分析最后一次比较情况。如果数组中存在target值,则最后一次left和right指针同时指向target值所在的位置;如果数组中没有target值,则最后一次left和right指针同时指向小于或大于target的第一个位置
2.搜索哪个范围,则使左右指针分别是此范围的左右边界。
例如:搜索范围是[l,h],则使left = l,right = h,保证搜索不会漏掉
3.第一是尝试熟练使用一种写法,比如左闭右开或左闭右闭(便于处理边界条件),尽量只保持这一种写法;第二是在刷题时思考如果最后区间只剩下一个数或者两个数,自己的写法是否会陷入死循环,如果某种写法无法跳出死循环,则考虑尝试另一种写法。实例看4
2.二分查找递归和非递归
非递归
public static int binarySearch(int[] arr,int target){
int left = 0,right = arr.length - 1;
while (left <= right){
int mid = left + (right - left) / 2;
if (arr[mid] == target){
return mid;
}else if (arr[mid] < target){
left = mid + 1;
}else{
right = mid - 1;
}
}
return -1;
}
递归
//递归
public static int binarySearch(int left,int right,int[] arr,int target){
if (left> right){
return -1;
}
int mid = left + (right - left) / 2;
if (arr[mid] == target){
return mid;
}else if (arr[mid] < target){
return binarySearch(mid + 1,right,arr,target);
}else{
return binarySearch(left,mid - 1,arr,target);
}
}
3.二分查找(力扣704)
最简单的二分查找
public static int searchInsert(int[] nums, int target) {
int len = nums.length;
int left = 0,right = len - 1;
while (left <= right){
int mid = left + (right - left) / 2;
if (nums[mid] == target){
return mid;
}else if (nums[mid] < target){
left = mid + 1;
}else if (nums[mid] > target){
right = mid - 1;
}
}
return -1;
}
4.搜索插入位置(力扣35)
//1.左闭右闭
public static int searchInsert(int[] nums, int target) {
int left = 0,right = nums.length - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if (nums[mid] == target){
return mid;
}else if (nums[mid] < target){
left = mid + 1;
}else if (nums[mid] > target){
right = mid - 1;
}
}
//return right + 1 也是对的
return left;
}
//2.左闭右开
public static int searchInsert(int[] nums, int target) {
int left = 0,right = nums.length;
while (left < right){
int mid = left + (right - left) / 2;
if (nums[mid] == target){
return mid;
}else if (nums[mid] < target){
left = mid + 1;
}else if (nums[mid] > target){
right = mid;
}
}
//return left 也可以
return right;
}
解析:以[1,3,5,6],target = 5 和 [1,3,5,6],target = 2 为例
1.左闭右闭
最终left==right,范围内只剩一个元素。如果数组中存在target,直接返回target的位置;如果数组中没有target:如果left=right=第一个小于target的位置,left = mid + 1=插入的位置,right=第一个小于target的位置,所以最终返回left或是right+1都可以;如果left=right=第一个大于target的位置,right = mid - 1,实际插入元素的位置为right + 1或是left
2.左闭右开
最终left<right,范围内剩下两个元素,mid=left,如果数组中存在target,直接返回target的位置;如果数组中没有target:则left位置<target,left = mid + 1,此时left=rght,因此返回left和right都可以
5.在排序数组中查找元素的第一个和最后一个位置(力扣34)
//1.暴力
public static int[] searchRange(int[] nums, int target) {
int len = nums.length;
if (len == 0) {
return new int[]{-1, -1};
}
if (nums[0] > target || nums[len - 1] < target) {
return new int[]{-1, -1};
}
int left = -1, right = -1;
boolean flag = true;
for (int i = 0; i < len; i++) {
if (flag) {
if (nums[i] == target) {
left = i;
flag = false;
}
}
if (nums[i] == target) {
right = i;
}
}
return new int[]{left, right};
}
//2.二分
public static int[] searchRange(int[] nums, int target) {
int leftBorder = searchLeft(nums, target);
int rightBorder = searchRight(nums, target);
//数组中存在target
if (rightBorder - leftBorder >= 0) {
return new int[]{leftBorder, rightBorder};
}
//数组中不存在target && 数组长度为0
return new int[]{-1, -1};
}
public static int searchLeft(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
//return left
return right + 1;
}
public static int searchRight(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] <= target) {
left = mid + 1;
}
}
//return right
return left - 1;
}
//3.二分优化
public static int[] searchRange(int[] nums, int target) {
//寻找左边界
int leftIndex = binarySearch(nums,target,true);
//寻找右边界
int rightIndex = binarySearch(nums,target,false) - 1;
//因为想要搜寻的目标值,有可能不在数组中,因此要对返回值进行判断
if (leftIndex <= rightIndex){
return new int[]{leftIndex,rightIndex};
}else{
return new int[]{-1,-1};
}
}
/*
* 1.寻找左边界,就是寻找第一个大于等于目标值的下标
* 2.寻找右边界,就是寻找第一个大于目标值的下标 - 1
* */
public static int binarySearch(int[] nums,int target,boolean lower){
//ans = nums.length,针对于 nums={1},target = 1的情况
int left = 0,right = nums.length - 1,ans = nums.length;
while (left <= right){
int mid = left + (right - left) / 2;
if (nums[mid] > target ||(lower && nums[mid] >= target)){
right = mid - 1;
ans = mid;
}else{
left = mid + 1;
}
}
return ans;
}
6.x 的平方根(力扣69)
//思路:找到第一个大于目标值的下标再-1
public static int mySqrt(int x) {
int left = 0,right = x;
while (left <= right){
int mid = left + (right - left) / 2;
//注意mid * mid 有可能超出int的范围
if ((long)mid * mid <= x){
left = mid + 1;
}else{
right = mid - 1;
}
}
//return right;
return left - 1;
}
1.7.有效的完全平方数(力扣367)
//1.暴力,超出时间限制
public static boolean isPerfectSquare(int num) {
for (int i = 1; i * i <= num; i++) {
if (i * i == num){
return true;
}
}
return false;
}
//2.(还是超出时间限制)
public static boolean isPerfectSquare(int num) {
int subNum = 1;
while (num > 0) {
num -= subNum;
subNum += 2;
}
return num == 0;
}
//3.二分
public static boolean isPerfectSquare(int num) {
int left = 0,right = num;
while(left <= right){
int mid = left + (right - left) / 2;
long sum = (long)mid * mid;
if (sum == num){
return true;
}else if (sum > num){
right = mid -1;
}else if (sum < num){
left = mid + 1;
}
}
return false;
}
//4.二分优化
public static boolean isPerfectSquare1(int num) {
if (num < 2) {
return true;
}
//num=2 num=3 直接返回false,因此right=num/2即可
long left = 2, right = num / 2, mid, sum;
while (left <= right) {
mid = left + (right - left) / 2;
sum = mid * mid;
if (sum == num) {
return true;
} else if (sum > num) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return false;
}
//5.牛顿迭代法
public static boolean isPerfectSquare2(int num) {
if (num < 2) {
return true;
}
long x = num / 2;
while (x * x > num) {
x = (x + num / x) / 2;
}
return (x * x == num);
}
二.移除元素
1.移除元素(力扣27)
很简单
//1.双指针
public static int removeElement(int[] nums, int val) {
int left = 0,right = nums.length - 1;
while (left <= right){
if (nums[left] == val){
nums[left] = nums[right];
right --;
}else{
left ++;
}
}
//或者是返回 right + 1;
return left;
}
//2.双指针(快慢指针)
//找到一个不等于val的,就赋值到数组前面,slowIndex记录个数
//两个指针都从左端出发
public static int removeElement(int[] nums, int val) {
int slowIndex = 0;
int fastIndex;
for (fastIndex = 0;fastIndex < nums.length;fastIndex ++){
if (nums[fastIndex] != val){
nums[slowIndex] = nums[fastIndex];
slowIndex ++;
}
}
return slowIndex;
}
2.移动零(力扣283)
//1.双指针(自己想的)
public static int[] moveZeroes(int[] nums) {
int slowIndex = 0;
int fastIndex;
for (fastIndex = 0;fastIndex < nums.length;fastIndex ++){
if (nums[fastIndex] != 0){
nums[slowIndex] = nums[fastIndex];
if (slowIndex != fastIndex){
nums[fastIndex] = 0;
}
slowIndex ++;
}
}
return nums;
}
//2.
//从前往后遍历,index记录不为0的数量
//全部遍历完成之后,依次补0
public static void moveZeroes(int[] nums) {
int len = nums.length;
int index = 0;
for (int i = 0; i < len; i++) {
if (nums[i] != 0){
nums[index] = nums[i];
index ++;
}
}
for (int i = index; i < len; i++) {
nums[i] = 0;
}
}
3.比较含退格的字符串(力扣844)
这个题有点绕,要思考一下
//1.使用栈的思想,使用StringBuffer来实现
public boolean backspaceCompare(String s, String t) {
return getString(s).equals(getString(t));
}
public String getString(String str){
StringBuffer sb = new StringBuffer();
int len = str.length();
for(int i = 0;i < len;i ++){
char c = str.charAt(i);
//如果不为‘#’,就将此字符加入到StringBuffer中
if(c != '#'){
sb.append(c);
//如果为‘#’,并且StringBuffer的长度不为0,就将最后一个元素删除
}else{
if(sb.length() != 0){
sb.deleteCharAt(sb.length() - 1);
}
}
}
return sb.toString();
}
//2.双指针
//false两种情况:
//1.比较两个字符不相等 2.一个字符串已经遍历完,另一个还没有遍历完
//整体思路是从后往前遍历
public static boolean backspaceCompare(String s, String t) {
int i = s.length() - 1, j = t.length() - 1;
int skipS = 0, skipT = 0;
while (i >= 0 || j >= 0) {
while (i >= 0) {
//如果当前字符是'#',计数
if (s.charAt(i) == '#') {
skipS++;
i--;
//消耗掉'#'
} else if (skipS > 0) {
skipS--;
//通过i--实现退格
i--;
} else {
break;
}
}
while (j >= 0) {
if (t.charAt(j) == '#') {
skipT++;
j--;
} else if (skipT > 0) {
skipT--;
j--;
} else {
break;
}
}
if (i >= 0 && j >= 0) {
//如果两个字符不相等,返回false
if (s.charAt(i) != t.charAt(j)) {
return false;
}
//如果一个已经遍历完,另一个还没有遍历完,返回false
} else if(i >= 0 || j >= 0){
return false;
}
i--;
j--;
}
return true;
}
4.有序数组的平方(力扣977)
//1.借助排序算法
public static int[] sortedSquares(int[] nums) {
int len = nums.length;
int index = 0;
for (int i = 0; i < len; i++) {
nums[index ++] = nums[i] * nums[i];
}
Arrays.sort(nums);
return nums;
}
//2.双指针
public static int[] sortedSquares(int[] nums) {
int len = nums.length;
int[] res = new int[len];
int index = len - 1;
int left = 0,right = len - 1;
while (left <= right){
if (nums[left] * nums[left] >= nums[right] * nums[right]){
res[index --] = nums[left] * nums[left];
left ++;
}else{
res[index --] = nums[right] * nums[right];
right --;
}
}
return res;
}
三.长度最小的子数组
这里主要涉及滑动窗口的思想
1.长度最小的子数组(力扣209)
//1.暴力(自己想的)以每一个元素为起点,找到满足条件的长度,从中取出最小值
public static int minSubArrayLen(int target, int[] nums) {
int len = nums.length;
int smallLength = Integer.MAX_VALUE;
for (int i = 0; i < len; i++) {
int count = 0,sum = 0;
for (int j = i; j < len; j++) {
sum += nums[j];
count ++;
if (sum >= target){
smallLength = Math.min(smallLength,count);
break;
}
}
}
return smallLength == Integer.MAX_VALUE ? 0 : smallLength;
}
第二种写法需要知道java中二分法的写法,下面是java中二分法的源代码
public static int binarySearch(int[] a, int key) {
return binarySearch0(a, 0, a.length, key);
}
// Like public version, but without range checks.
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
int key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
//2.前缀和+二分法
//注意java默认二分法的写法
public static int minSubArrayLen(int s, int[] nums) {
int len = nums.length;
//如果数组长度为0,直接返回0
if (len == 0){
return 0;
}
//构建一个前缀和数组
int[] num = new int[len + 1];
for (int i = 1; i <= len; i++) {
num[i] = num[i - 1] + nums[i - 1];
}
int smallLength = Integer.MAX_VALUE;
for (int i = 1; i < len + 1; i++) {
int target = s + num[i - 1];
int index = Arrays.binarySearch(num,target);
if (index < 0){
index = -index - 1;
}
//1.此时找不到大于或者是等于target的位置,不满足index <= len 条件
//2.此时找到一个大于或者是等于target的位置,满足index <= len 条件,进行比较
if (index <= len){
smallLength = Math.min(smallLength,index - i + 1);
}
}
return smallLength == Integer.MAX_VALUE ? 0 : smallLength;
}
//3.滑动窗口
public static int minSubArrayLen(int target, int[] nums) {
int len = nums.length;
if(len == 0) return 0;
//初始两个指针都指向最左边
int start = 0,end = 0,sum = 0;
int smallLength = Integer.MAX_VALUE;
while (end < len){
//如果sum<target,就将nums[end]加到sum中
sum += nums[end];
//如果sum>=target,就从sum中减去nums[start],并更新最小长度,直到sum<target
while (sum >= target){
smallLength = Math.min(smallLength,end - start + 1);
sum -= nums[start];
start ++;
}
end ++;
}
return smallLength == Integer.MAX_VALUE ? 0 : smallLength;
}
2.水果成篮(力扣904)
//1.滑动窗口
//题意:求最多包含两种元素的最长连续子序列
public static int totalFruit(int[] fruits) {
int len = fruits.length;
//滑动窗口始末位置和最长连续子序列的长度
int start = 0, end = 0, maxLength = 0;
//始末位置元素值
int startNum = fruits[start],endNum = fruits[end];
while (end < len) {
//如果新加入的元素已经包含,末位置向后移动,更新长度
if (fruits[end] == startNum || fruits[end] == endNum){
maxLength = Math.max(maxLength,end - start + 1);
end ++;
//如果新加入的元素没有包含,更新始末位置和长度
}else{
//更新开始位置
//当前end位置的值是一个新值,直接更新开始位置是end的前一个值,此时包含两个值
start = end - 1;
//更新开始值
startNum = fruits[start];
//这一步很重要,看开始位置的前面有没有和开始位置相同的值
while (start >= 1 && fruits[start - 1] == startNum){
start --;
}
//更新结束值
endNum = fruits[end];
maxLength = Math.max(maxLength,end - start + 1);
}
}
return maxLength;
}
3.最小覆盖子串(力扣76)
/1.滑动窗口
// 完全看的解释(https://leetcode-cn.com/problems/minimum-window-substring/solution/tong-su-qie-xiang-xi-de-miao-shu-hua-dong-chuang-k/)
public static String minWindow(String s, String t) {
if (s.length() < t.length()) {
return "";
}
//维护两个数组,记录已有字符串指定字符的出现次数,和目标字符串指定字符的出现次数
//ASCII表总长128
int[] need = new int[128];
int[] have = new int[128];
//将目标字符串指定字符的出现次数记录
for (int i = 0; i < t.length(); i++) {
need[t.charAt(i)]++;
}
//分别为左指针,右指针,最小长度(初始值为一定不可达到的长度)
//已有字符串中目标字符串指定字符的出现总频次以及最小覆盖子串在原字符串中的起始位置
int left = 0, right = 0, min = s.length() + 1, count = 0, start = 0;
while (right < s.length()) {
char r = s.charAt(right);
//说明该字符不被目标字符串需要,此时有两种情况
// 1.循环刚开始,那么直接移动右指针即可,不需要做多余判断
// 2.循环已经开始一段时间,此处又有两种情况
// 2.1 上一次条件不满足,已有字符串指定字符出现次数不满足目标字符串指定字符出现次数,那么此时
// 如果该字符还不被目标字符串需要,就不需要进行多余判断,右指针移动即可
// 2.2 左指针已经移动完毕,那么此时就相当于循环刚开始,同理直接移动右指针
if (need[r] == 0) {
right++;
continue;
}
//当且仅当已有字符串目标字符出现的次数小于目标字符串字符的出现次数时,count才会+1
//是为了后续能直接判断已有字符串是否已经包含了目标字符串的所有字符,不需要挨个比对字符出现的次数
if (have[r] < need[r]) {
count++;
}
//已有字符串中目标字符出现的次数+1
have[r]++;
//移动右指针
right++;
//当且仅当已有字符串已经包含了所有目标字符串的字符,且出现频次一定大于或等于指定频次
while (count == t.length()) {
//挡窗口的长度比已有的最短值小时,更改最小值,并记录起始位置
if (right - left < min) {
min = right - left;
start = left;
}
char l = s.charAt(left);
//如果左边即将要去掉的字符不被目标字符串需要,那么不需要多余判断,直接可以移动左指针
if (need[l] == 0) {
left++;
continue;
}
//如果左边即将要去掉的字符被目标字符串需要,且出现的频次正好等于指定频次,那么如果去掉了这个字符,
//就不满足覆盖子串的条件,此时要破坏循环条件跳出循环,即控制目标字符串指定字符的出现总频次(count)-1
if (have[l] == need[l]) {
count--;
}
//已有字符串中目标字符出现的次数-1
have[l]--;
//移动左指针
left++;
}
}
//如果最小长度还为初始值,说明没有符合条件的子串
if (min == s.length() + 1) {
return "";
}
//返回的为以记录的起始位置为起点,记录的最短长度为距离的指定字符串中截取的子串
return s.substring(start, start + min);
}
四.螺旋矩阵II
1.螺旋矩阵(力扣54)
在这里要注意区分所给矩阵是n * n 还是 m * n (m != n),后者需要加额外的条件判断
//方向优先级右下左上
//遍历过的都令其值为233
int m = matrix.length, n = matrix[0].length;
int i = 0, j = 0;
List<Integer> list = new ArrayList<>();
int dirction = 0;//0代表右,1代表下,2代表左,3代表上
for (int cur = 0; cur < m * n; cur++) {
list.add(matrix[i][j]);
matrix[i][j] = 233;
if (dirction == 0 && (j == n - 1 || matrix[i][j + 1] == 233)) {
dirction = 1;
}
if (dirction == 1 && (i == m - 1 || matrix[i + 1][j] == 233)){
dirction = 2;
}
if (dirction == 2 && (j == 0 || matrix[i][j - 1] == 233)){
dirction = 3;
}
if (dirction == 3 && (i == 0 || matrix[i - 1][j] == 233)){
dirction = 0;
}
if (dirction == 0){
j ++;
}
if (dirction == 1){
i ++;
}
if (dirction == 2){
j --;
}
if (dirction == 3){
i --;
}
}
return list;
2.螺旋矩阵 II(力扣59)
//1.和螺旋矩阵一样的思路
public static int[][] generateMatrix(int n) {
int[][] res = new int[n][n];
int r = 0, c = 0;
//方向优先级右下左上
//0代表右,1代表下,2代表左,3代表上
int direction = 0;
for (int i = 1; i <= n * n; i++) {
res[r][c] = i;
//数组初始化默认为0
if (direction == 0 && (c == n - 1 || res[r][c + 1] != 0)){
direction = 1;
}
if (direction == 1 && (r == n - 1 || res[r + 1][c] != 0)){
direction = 2;
}
if (direction == 2 && (c == 0 || res[r][c - 1] != 0)){
direction = 3;
}
if (direction == 3 && (r == 0 || res[r- 1][c] != 0)){
direction = 0;
}
if (direction == 0){
c ++;
}
if (direction == 1){
r ++;
}
if (direction == 2){
c --;
}
if (direction == 3){
r --;
}
}
return res;
}
//2.分别定义四个边界
public static int[][] generateMatrix(int n) {
int[][] res = new int[n][n];
//上下左右四个边界
int up = 0,down = n - 1,left = 0,right = n - 1,index = 1;
while (index <= n * n){
//从左往右,left -> right
for (int i = left; i <= right; i++) {
res[up][i] = index ++;
}
up ++;
//从上往下,up -> down
for (int i = up; i <= down; i++) {
res[i][right] = index ++;
}
right --;
//从右往左 right -> left
for (int i = right; i >= left ; i--) {
res[down][i] = index ++;
}
down --;
//从下往上, down -> up
for (int i = down; i >= up ; i--) {
res[i][left] = index ++;
}
left ++;
}
return res;
3.顺时针打印矩阵(剑指 Offer 29)
本题与力扣 54 题相同
//1.定义四个边界
public static int[] spiralOrder(int[][] matrix) {
int m = matrix.length;
if (m == 0){
return new int[0];
}
int n = matrix[0].length;
int[] res = new int[m * n];
int up = 0, down = m - 1, left = 0, right = n - 1, index = 0;
while (index < m * n) {
for (int i = left; i <= right; i++) {
res[index++] = matrix[up][i];
}
//对于n * n 矩阵,下面这个if条件判断不用加
//对于m * n 矩阵(m != n),下面这个if条件必须加,否则会出现数组角标越界异常
if (++up > down){
break;
}
for (int i = up; i <= down; i++) {
res[index++] = matrix[i][right];
}
if (--right < left){
break;
}
for (int i = right; i >= left; i--) {
res[index++] = matrix[down][i];
}
if (--down < up){
break;
}
for (int i = down; i >= up; i--) {
res[index++] = matrix[i][left];
}
if (++left > right){
break;
}
}
return res;
五.笔试原题
2023 中兴笔试题
1.面试题 17.19. 消失的两个数字
public int[] missingTwo(int[] nums) {
int len = nums.length,a = 1,b = 1;
for(int i = 0;i < len;i ++){
// 因为下面负数的操作会影响到后面,所以取为绝对值
int j = Math.abs(nums[i]) - 1;
// 如果数组中存在此数字,将对应位置的数字变为负数
if(j < len){
nums[j] *= -1;
// 数组长度为len,a和b分别为超出范围的第一个和第二个
}else if(j == len){
a = -1;
}else{
b = -1;
}
}
int[] res = new int[2];int index = 0;
for(int i = 0;i < len; i++){
// 只要此位置为正,说明没有出现过,记录
if(nums[i] > 0){
res[index ++] = i + 1;
}
}
if(a == 1) res[index ++] = len + 1;
if(b == 1) res[index ++] = len + 2;
return res;
}
2. 401. 二进制手表
public List<String> readBinaryWatch(int turnedOn) {
List<String> res = new ArrayList<>();
// 一共有10个灯,所以有1024种情况
for(int i = 0;i < 1024;i ++){
// 分别获取小时和分钟
int hour = i >> 6, minute = i & 63;
if(hour < 12 && minute < 60 && Integer.bitCount(i) == turnedOn){
res.add(hour + ":" + (minute < 10 ? "0" : "") + minute);
}
}
return res;
}