1.剑指 Offer 11. 旋转数组的最小数字
输入: numbers = [3,4,5,1,2]
输出: 1
输入: numbers = [2,2,2,0,1]
输出: 0
可直接遍历一遍输出其中最小的数字,复杂度是O(n),考虑优化,查找有序数组,用二分查找。思考旋转数组的性质,最小值左边的元素一定是最大值,并且最小值右边的元素一定都小于等于最小值左边的元素。二分是把低位元素设为low,高位为heigh,中间值为mid。
这样就有三种情况,mid是最小值,mid在最小值右边,mid在最小值左边。逐个分析,如果number[heigh]>number[mid],就说明最小值一定在mid左边或者就是mid,而如果number[heigh]<number[mid],就说明最小值一定在mid的右边,到这可以写出一部分代码:
var minArray = function(numbers) {
let low = 0
let heigh = numbers.length
while(low <=heigh) {
let mid = Math.floor((heigh-low)/2) + low
if(numbers[mid] < numbers[heigh]) {
heigh = mid
} else if(numbers[mid] > numbers[heigh]) {
low = mid+1
}
}
};
那么还有一种情况,number[heigh]===number[mid]的时候,目标值在mid左边或者右边都有可能
这时候,就需要将heigh一步一步移动去探测具体目标值在哪,当右指针到了左指针左边,就代表搜索完了,此时numbers[low]就是我们要的位置。
var minArray = function(numbers) {
let low = 0
let heigh = numbers.length
while(low <=heigh) {
mid = Math.floor((heigh-low)/2) + low
if(numbers[mid] < numbers[heigh]) {
heigh = mid
} else if(numbers[mid] > numbers[heigh]) {
low = mid+1
} else {
heigh -= 1
}
}
return numbers[low]
};
2.剑指 Offer 12. 矩阵中的路径
遇到图搜索,目前已知的就是DFS和BFS。这题的基本思路就是dfs和回溯,每次向外一层搜索,如果遇到对的就继续,如果不对就回溯回来,向下一个方向继续搜索,同时为了优化性能还需要标记走过且不匹配的路径。 首先先按照dfs模板把深度优先搜索给写了,第一步,建立visited表,建立遍历方向表
var exist = function(board, word) {
const m = board.length
const n = board[0].length
const visited = new Array(m).fill(0).map(()=>new Array(n).fill(false))
const dir = [[0,1],[1,0],[0,-1],[-1,0]]
};
第二步,根据dfs模板,建立dfs函数对图进行搜索,如果遇到匹配的,就继续搜索,否则就回溯,并把visited设置回false。
var exist = function(board, word) {
const m = board.length
const n = board[0].length
const visited = new Array(m).fill(0).map(()=>new Array(n).fill(false))
const dir = [[0,1],[1,0],[0,-1],[-1,0]]
const dfs = (x,y,k)=> {
if(board[x][y] != word[k]) return false
else if(k === word.length-1) return true
visited[x][y] = true
let result = false
for(const [dx,dy] of dir) {
let i = x + dx
let j = y + dy
if(i>=0 && i<m && j>=0 && j<n) {
if(!visited[i][j]) {
const flag = dfs(i,j,k+1)
if(flag) {
result = true
break
}
}
}
}
visited[x][y] = false
return result
}
};
最后运行主函数用两个for循环遍历整张表
var exist = function(board, word) {
const m = board.length
const n = board[0].length
const visited = new Array(m).fill(0).map(()=>new Array(n).fill(false))
const dir = [[0,1],[1,0],[0,-1],[-1,0]]
const dfs = (x,y,k)=> {
if(board[x][y] != word[k]) return false
else if(k === word.length-1) return true
visited[x][y] = true
let result = false
for(const [dx,dy] of dir) {
let i = x + dx
let j = y + dy
if(i>=0 && i<m && j>=0 && j<n) {
if(!visited[i][j]) {
const flag = dfs(i,j,k+1)
if(flag) {
result = true
break
}
}
}
}
visited[x][y] = false
return result
}
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
const flag = dfs(i, j, 0);
if (flag) {
return true;
}
}
}
return false;
};
3.剑指 Offer 13. 机器人的运动范围
也是一道dfs搜索题目
var movingCount = function(m, n, k) {
const visited = Array(m).fill(0).map(() => Array(n).fill(false))
const digitSum = (n) => {
let ans = 0
while(n) {
ans += n % 10
n = Math.floor(n/10)
}
return ans
}
const dfs = (x,y) => {
if(digitSum(x) + digitSum(y) > k || x >= m || y >= n || visited[x][y]) return 0
visited[x][y] = true
return 1 + dfs(x+1,y) + dfs(x,y+1)
}
return dfs(0,0)
};
4.剑指 Offer 14- I. 剪绳子
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
(2 <= n <= 58)
是一个数学问题,利用“均值不等式”可知尽可能切成长度为3的段是最优解,次优2,最差1(然而真正做题的时候估计只能靠例子来猜到底几段好),那么斗胆将这道题简化为,如何把一段绳子尽可能分成长度3,2,1,3最优,1最次,利用贪心来做。
var cuttingRope = function(n) {
if(n<=3) return n-1
let a = Math.floor(n/3)
let b = n%3
if(b === 0)return Math.pow(3,a)//3的倍数,直接返回题目要求的乘积,同时可3先3
if(b === 1) return Math.pow(3, a-1) * 4//先拆3的倍数,剩1个,将3+1替换为2+2,因为4>3
return Math.pow(3,a)*2//有3先3,剩2就直接乘进去了
};
5.剑指 Offer 14- II. 剪绳子 II
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
(2 <= n <= 1000)
这道题的不同在于存在大数越界问题。
评论区有人贴了一个用bigInt的方法,值得学习,这是我第一次在实际做题中看到bigInt的用处
var cuttingRope = function (n) {
if (n <= 3) {
return n - 1;
}
const MOD = BigInt(1000000007);
const a = BigInt(Math.floor(n / 3));
const b = BigInt(n % 3);
const get = ((x, a, mod) => {
let res = 1n;
while (a > 0n) {
// console.log(x, a, res, mod)
if (a % 2n === 1n) {
res = (res * x) % mod;
}
x = (x * x) % mod;
a = a % 2n === 1n ? (a - 1n) / 2n : a / 2n;
}
return res;
})
if (b === 0n) {
return get(3n, a, MOD);
} else if (b === 1n) {
return (get(3n, a - 1n, MOD) * 4n) % MOD;
} else if (b === 2n) {
return (get(3n, a, MOD) * 2n) % MOD;
}
};
对待大数可以通过每步取余来做,如果只最后一步取余的话,可能会在中间出现超过范围的问题
var cuttingRope = function(n) {
if(n === 2) return 1
if(n === 3) return 2
let res = 1
while(n>4) {
res *= 3
res %= 1000000007
n-=3
}
return res*n%1000000007
};
6.剑指 Offer 15. 二进制中1的个数
这是一道位运算问题,可以用循环检查二进制位的方法,让n与 2^i进行与运算,只有第i位为1时结果不为0,于是可以判断其中1的个数
var hammingWeight = function(n) {
let res = 0;
for (let i = 0; i < 32; i++) {
if ((n & (1 << i)) !== 0) {
res++;
}
}
return res;
};
7.剑指 Offer 16. 数值的整数次方
输入: x = 2.00000, n = 10
输出: 1024.00000
输入: x = 2.10000, n = 3
输出: 9.26100
输入: x = 2.00000, n = -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
不用库函数,实现Math.pow() 使用快速幂方法,什么是快速幂呢?比如我们计算2^20,没必要从从1次方算20次到20次方,可以1-2-4-8-16-20次方的过程,计算快速幂,其实本质是把数字降幂的过程。
var myPow = function(x, n) {
if (n == 0) return 1;
if (n == -1) return 1.0 / x;
//奇数
if (n & 1) return x * myPow(x*x, n>>1); //右移代替除以2,位与代替求余运算来判断奇偶
//偶数
else return myPow(x*x, n>>1)
}