剑指Offer一刷(下)

模拟

题05 替换空格

链接: link

StringBuilder res = new StringBuilder();
for(Character c : s.toCharArray()){
    if(c == ' '){
        res.append("%20");
    }else res.append(c);
}
return res.toString();

题17 打印从1到最大的n位数

链接: link

int digit = 1, maxNum = 0; // 位因子,最后一个数的大小
for(int i = 0; i < n; i++){ 
    maxNum += digit * 9;
    digit *= 10;
}
int[] res = new int[maxNum];
for(int i = 0; i < maxNum; i++){
    res[i] = i+1;
}
return res;

题20 表示数值的字符串

链接: link
有限状态自动机。以下代码来自力扣题解。

Map[] states = {
    new HashMap<>() {{ put(' ', 0); put('s', 1); put('d', 2); put('.', 4); }}, // 0.
    new HashMap<>() {{ put('d', 2); put('.', 4); }},                           // 1.
    new HashMap<>() {{ put('d', 2); put('.', 3); put('e', 5); put(' ', 8); }}, // 2.
    new HashMap<>() {{ put('d', 3); put('e', 5); put(' ', 8); }},              // 3.
    new HashMap<>() {{ put('d', 3); }},                                        // 4.
    new HashMap<>() {{ put('s', 6); put('d', 7); }},                           // 5.
    new HashMap<>() {{ put('d', 7); }},                                        // 6.
    new HashMap<>() {{ put('d', 7); put(' ', 8); }},                           // 7.
    new HashMap<>() {{ put(' ', 8); }}                                         // 8.
};
int p = 0; // 初始状态0
char t;  // 记录字符类型
for(char c : s.toCharArray()) {
    if(c >= '0' && c <= '9') t = 'd';  // 数字
    else if(c == '+' || c == '-') t = 's';  // 符号
    else if(c == 'e' || c == 'E') t = 'e';  // e或E
    else if(c == '.' || c == ' ') t = c; // .或空格
    else t = '?';
    if(!states[p].containsKey(t)) return false;
    p = (int)states[p].get(t);
}
return p == 2 || p == 3 || p == 7 || p == 8; // 最终合法状态

题29 顺时针打印矩阵

链接: link

if(matrix.length == 0) return new int[0];
int up=0, right=matrix[0].length-1, down=matrix.length-1, left=0, index=0;
int[] res = new int[(right+1) * (down+1)];
while(true){
    // 从左往右遍历,结束上边界加1
    for(int i=left; i<=right; i++){
        res[index++] = matrix[up][i];
    }if(++up > down) break;
    // 从上往下遍历,结束右边界减1
    for(int i=up; i<=down; i++){
        res[index++] = matrix[i][right];
    }if(--right < left) break;
    // 从右往左遍历,结束下边界减1
    for(int i=right; i>=left; i--){
        res[index++] = matrix[down][i];
    }if(--down < up) break;
    // 从下往上遍历,结束左边界加1
    for(int i=down; i>=up; i--){
        res[index++] = matrix[i][left];
    }if(++left > right) break;
}
return res;

题39 数组中出现次数超过一半的数字

链接: link

int count=0, candidate=nums[0]; // 候选人的得票数,当前候选人
for(int num : nums){
    if(num != candidate){ // 不是当前候选人的支持者
        count--; // 反对票
        if(count<0){ // 此时应该更换候选人
            candidate=num;
            count=0;
        }
    }else count++; // 同意票
}
return candidate;

题58 左旋转字符串

链接: link

StringBuilder res = new StringBuilder();
for(int i=n; i<n+s.length(); i++){
    res.append(s.charAt(i % s.length()));
}
return res.toString();

题61 扑克牌中的顺子

链接: link

Set<Integer> repeat = new HashSet<>();
int max = 0, min = 13;
for(int num : nums){
    if(num == 0) continue; // 大小王跳过
    max = Math.max(max, num);
    min = Math.min(min, num);
    if(repeat.contains(num)) return false; // 重复的数字一定不行
    repeat.add(num);
}
return max - min < 5; // 相差>=5一定不行

题66 构建乘积数组

链接: link

int n = a.length;
int[] res = new int[n];
int left = 1; // 记录当前下标左边所有数的乘积
for(int i=0; i<n; i++){
    res[i] = left;
    left *= a[i];
}
left = 1; // 记录当前下标右边所有数的乘积
for(int i=n-1; i>=0; i--){
    res[i] *= left;
    left *= a[i];
}
return res;

题67 把字符串转换成整数

链接: link

char[] c = str.trim().toCharArray(); // 去除前后空格
if(c.length == 0) return 0;
int res = 0, boundary = Integer.MAX_VALUE / 10; // 当前结果, 边界值214748364
int i = 1, sign = 1; // 默认有正号,从第1位开始计算
if(c[0] == '-') sign = -1; // 有负号
else if(c[0] != '+') i = 0; // 没有正负号的情况从第0位开始
for(int j = i; j < c.length; j++){
    if(c[j] < '0' || c[j] > '9') break; // 不是数字就跳出循环
    // 当前结果大于边界值或者虽等于边界值但加上个位数字会越界,返回最大/最小值
    if(res > boundary || res==boundary && c[j] > '7') return sign==1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; 
    res = res * 10 + (c[j] - '0'); // 计算当前数值
}
return res * sign; // 记得乘上正负号

排序

题45 把数组排成最小的数

链接: link

String[] strs = new String[nums.length];
for(int i=0; i<nums.length; i++){
    strs[i] = String.valueOf(nums[i]); // 转成字符串数组
}
// x+y<y+x,说明x应该放y左边
Arrays.sort(strs, (x, y) -> (x + y).compareTo(y + x));
StringBuilder res = new StringBuilder();
for(String s : strs){
    res.append(s);
}
return res.toString();

题49 丑数

链接: link

int a=0, b=0, c=0; // 初始位于第一个丑数处
int[] dp = new int[n];
dp[0] = 1; // 第一个丑数
for(int i=1; i<n; i++){
    int n2=dp[a]*2, n3=dp[b]*3, n5=dp[c]*5; // 每次分别能走2、3、5步
    dp[i] = Math.min(n2, Math.min(n3, n5)); // 下一个丑数是最近的
    if(dp[i]==n2) a++; // 这一步走过去的下标加1
    if(dp[i]==n3) b++;
    if(dp[i]==n5) c++;
}
return dp[n-1];

题51 数组中的逆序对

链接: link

int[] nums, temp;
public int reversePairs(int[] nums) {
    this.nums = nums;
    temp = new int[nums.length];
    return mergeSort(0, nums.length-1);
}

public int mergeSort(int left, int right){
    // 终止条件
    if(left>=right) return 0;
    // 递归划分
    int mid = (left + right) / 2;
    int res = mergeSort(left, mid) + mergeSort(mid+1, right);
    // 合并
    int i = left, j = mid+1;
    for(int k=left; k<=right; k++){
        temp[k] = nums[k]; // 复制数组
    }
    for(int k = left; k<=right; k++){
        if(i==mid+1) nums[k] = temp[j++]; // i先到结尾
        else if(j==right+1 || temp[i]<=temp[j]) nums[k] = temp[i++]; // j先到结尾或者非逆序
        else if(temp[i]>temp[j]){ // 逆序
            nums[k] = temp[j++];
            res += mid-i+1; // 统计逆序数
        }
    }
    return res;
}

位运算

题15 二进制中1的个数

链接: link

int res = 0;
while(n!=0){
    res += n&1;
    n >>>= 1; // 无符号右移
}
return res;

题56 数组中数字出现次数

链接: link

int resA=0, resB=0, n=0, m=1; 
for(int num : nums) n ^= num; // n是所有数异或的结果
while((n & m) ==0) m <<= 1; // m是n的第一位1出现位置
for(int num : nums){
    if((num & m) == 0) resA ^= num; // 在m处为0的是一组
    else if((num & m) ==m) resB ^= num; // 在m处为1的是一组
}
return new int[] {resA, resB};

题56 数组中数字出现次数2

链接: link

int[] bits = new int[32]; // 记录每一位的1的数量
for(int num : nums){
    for(int i=0; i<32; i++){
        bits[i] += num & 1;
        num >>>= 1;
    }
}
int res = 0;
for(int i=31; i>=0; i--){
    res <<= 1;
    res |= bits[i] % 3;  // 只出现一次的数字是1还是0
}
return res;

题64 求1+2+…+n

链接: link

int res = 0;
public int sumNums(int n) {
    boolean x = n > 1 && sumNums(n-1) > 0; // 使用短路与来终止递归
    res += n;
    return res;
}

题65 不用加减乘除做加法

链接: link

public int add(int a, int b) {
    if(b == 0) return a; // b为0时直接返回
    return add(a ^ b, (a & b) << 1); // 等价于add(无进位和(异或操作), 进位(与操作并左移一位))
}

二分

题11 旋转数组的最小数字

链接: link

int left = 0, right = numbers.length-1;
while(left < right){
    int mid = (left + right) / 2;
    if(numbers[mid] > numbers[right]) left = mid + 1; // 左半边有序
    else if(numbers[mid] < numbers[right]) right = mid; // 右半边有序
    else right--; // mid值和right值相等,去掉right值不影响
}
return numbers[left];

题53 在排序数组中查找数字

链接: link

public int search(int[] nums, int target) {
    return findRight(nums, target) - findRight(nums, target-1); // 两次的差即为target个数
}

public int findRight(int[] nums, int target){ // 找到第一个大于目标值的下标
    int left = 0, right = nums.length-1;
    while(left <= right){  // 注意这里的等号
        int mid = (left + right) / 2;
        if(nums[mid] <= target) left = mid + 1;
        else right = mid - 1;
    }
    return left;
}

题53 0到n-1中缺失的数字

链接: link

if(nums[0] != 0) return 0;
else if(nums[nums.length-1] != nums.length) return nums.length;
else{
    int left = 0, right = nums.length - 1;
    while(left <= right){ // 找到第一个大于下标的数
        int mid = (left + right) / 2;
        if(nums[mid] <= mid) left = mid + 1;
        else right = mid - 1;
    }
    return left;
}

快速幂

题16 数值的整数次方

链接: link

if(x == 0) return 0;
long b = n; // 防止n = -n时越界
double res = 1.0;
if(b < 0){ // 将指数变成正数
    x = 1 / x;
    b = -b;
}
while(b > 0){
    if((b & 1) == 1) res *= x; // 指数为奇数
    x *= x; // x = x^2
    b >>= 1; // b整除2
}
return res;

双指针

题03 数组中重复的数字

链接: link

int i = 0;
while(i < nums.length){
    if(nums[i] == i){ // 数字i在下标i
        i++;
        continue;
    }
    // 此时说明有重复元素nums[i]
    if(nums[i] == nums[nums[i]]) return nums[i]; 
    int temp = nums[i]; // 交换nums[i]和nums[nums[i]]
    nums[i] = nums[temp];
    nums[temp] = temp;
}
return -1;

题21 调整数组顺序使奇数位于偶数前面

链接: link

public int[] exchange(int[] nums) {
    if(nums.length <= 1) return nums;
    int i = 0, j = nums.length-1;
    while(i < j){
        while((nums[i] & 1) == 1 && i < j) i++; // 前面的奇数就跳过
        while((nums[j] & 1) == 0 && i < j) j--; // 后面的偶数就跳过
        if(i >= j) break;
        else if(nums[i] % 2 == 0 && nums[j] % 2 == 1){ // 此时一定是奇偶交换的情况
            swap(nums, i, j);
            i++;
            j--;
        }
    }
    return nums;
}

public void swap(int[] nums, int i, int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

题48 最长不含重复字符的子字符串

链接: link

// 哈希表
Map<Character, Integer> map = new HashMap<>();
int i = -1, res = 0;
for(int j = 0; j < s.length(); j++){
    if(map.containsKey(s.charAt(j))){
        i = Math.max(i, map.get(s.charAt(j))); // 左指针移动到重复字符处
    }
    map.put(s.charAt(j), j); // 将右指针指向字符加入哈希表
    res = Math.max(res, j - i);  // 更新结果
} 
return res;
// 数组
int[] map = new int[127];
int i = 0, j = 0;
int res = 0;
while(j < s.length()){
    if(map[s.charAt(j) - 0] == 0){ // 数组里未出现过的字符
        map[s.charAt(j) - 0] += 1;
        j++;
        res = Math.max(res, j - i); 
    }else{
        while(i < j && map[s.charAt(j) - 0] > 0){ // 找到j指向字符出现的位置
            map[s.charAt(i) - 0] -= 1;
            i++;
        }
    }
}
return res;

题57 和为s的连续正数序列

链接: link

List<int[]> res = new ArrayList<>();
int i = 1, j = 2; // 最小的两个数
int sum = 3;
while(j <= target / 2 + 1){ // j最大时结束
    if(sum < target){ // 增大右边界
        j++;
        sum += j;
    }
    else if(sum > target){  // 缩小左边界
        sum -= i;
        i++;
    }else{
        int[] path = new int[j - i + 1]; // 记录结果
        for(int k = i; k <= j; k++){
            path[k - i] = k;
        }
        res.add(path);
        j++;   // 记录完右边界右移一步
        sum += j;
    }
}
return res.toArray(new int[res.size()][]);

题58 翻转单词顺序

链接: link

s.trim(); // 去掉首尾空格
int i = s.length()-1, j = i; // 从后往前遍历
StringBuilder res =new StringBuilder();
while(i >= 0){
    while(i >= 0 && s.charAt(i) != ' ') i--; // 找到第一个空格
    res.append(s.substring(i+1, j+1) + " "); // 添加单词和一个空格
    while(i >= 0 && s.charAt(i) == ' ') i--; // 找到第一个非空格
    j = i;
}
return res.toString().trim();

搜索与回溯

题04 二维数组中的查找

链接: link

if(matrix.length == 0) return false;
int x = 0, y = matrix[0].length - 1; // 初始位于矩阵右上角
while(x < matrix.length && y >= 0){
    if(matrix[x][y] < target) x++; // 往下一格
    else if(matrix[x][y] > target) y--; // 往左一格
    else return true;
}
return false;

题12 矩阵中的路径

链接: link

public boolean exist(char[][] board, String word) {
    for(int i=0; i<board.length; i++){
        for(int j=0; j<board[0].length; j++){
            if(backTrack(board, i, j, word, 0)) return true;
        }
    }
    return false;
}

public boolean backTrack(char[][] board, int x, int y, String word, int index){
    if(!isValid(board, x, y) || board[x][y] == '.' || board[x][y] != word.charAt(index)){
        return false;
    }
    if(index == word.length()-1) return true; // 找到单词
    char temp = board[x][y];
    board[x][y] = '.';  // 标记为已走过
    // 分别往上下左右尝试走一步
    boolean res = backTrack(board, x-1, y, word, index+1) || backTrack(board, x+1, y, word, index+1)
    || backTrack(board, x, y-1, word, index+1) || backTrack(board, x, y+1, word, index+1);
    board[x][y] = temp; // 回溯
    return res;
}

public boolean isValid(char[][] board, int i, int j){ // 判断越界
    return i>=0 && i<board.length && j>=0 && j<board[0].length;
}

题13 机器人的运动范围

链接: link

int m, n, k;
boolean[][] visited;
public int movingCount(int m, int n, int k) {
    this.m = m; this.n = n; this.k = k;
    visited = new boolean[m][n];
    return backTrack(0, 0, 0, 0);
}

public int backTrack(int i, int j, int sumi, int sumj){
    // 边界:越界,数位和大于k,已经访问过
    if(i >= m || j >= n || sumi + sumj > k || visited[i][j]) return 0;
    visited[i][j] = true; // 标记当前位置已访问
    // 9->10,19->20会产生数位和的突变,否则都是加1操作
    int right = backTrack(i, j+1, sumi, (j + 1) % 10 == 0 ? sumj-8 : sumj+1); // 右边可走格子数
    int down = backTrack(i+1, j, (i + 1) % 10 == 0 ? sumi-8 : sumi+1, sumj); // 下边可走格子数
    return 1 + right + down;
}

题38 字符串的排列

链接: link

List<String> res = new ArrayList<>();
StringBuilder path = new StringBuilder();
boolean[] pathUsed; // 用来对路径上元素去重
public String[] permutation(String s) {
    pathUsed = new boolean[s.length()];
    backTrack(s);
    return res.toArray(new String[res.size()]);
}

public void backTrack(String s){
    if(path.length() == s.length()){ 
        res.add(path.toString()); // 添加结果
        return;
    }
    int[] used = new int[26]; //  用来对同一层元素去重
    for(int i=0; i<s.length(); i++){
        // 同层元素已被使用 或 路径元素已经使用
        if(used[s.charAt(i) - 'a']==1 || pathUsed[i]) continue;
        used[s.charAt(i) - 'a'] = 1;

        pathUsed[i] = true;
        path.append(s.charAt(i));
        backTrack(s);
        path.deleteCharAt(path.length()-1);
        pathUsed[i] = false;
    }
}

动态规划

题10 斐波那契数列

链接: link

if(n == 0) return 0;
if(n == 1) return 1;
long a = 0, b = 1;
long c = 0;
for(int i = 2; i <= n; i++){
    c = (a + b) % 1000000007; // 取余
    a = b;
    b = c;
}
return (int)c;

题10 青蛙跳台阶问题

链接: link

if(n == 0 || n == 1) return 1;
long a = 1, b = 1, c = 0;
for(int i = 2; i <= n; i++){
    c = (a + b) % 1000000007;
    a = b;
    b = c;
}
return (int) c;

题19 正则表达式匹配

链接: link

int sLen = s.length(), pLen = p.length();
// dp[i][j]表示s中到下标i-1的字符串能否由p中到j-1的字符串匹配
boolean[][] dp = new boolean[sLen+1][pLen+1];
dp[0][0] = true; // 初始化
// dp[0][j]只有在1个或多个"#*"时才能匹配空串
for(int j=2; j<=pLen; j+=2){
    if(p.charAt(j-1) == '*'){
        dp[0][j] = dp[0][j-2];
    }
}
for(int i=1; i<=sLen; i++){
    for(int j=1; j<=pLen; j++){
        char sc = s.charAt(i-1); // s的最后一个字符
        char pc = p.charAt(j-1); // p的最后一个字符
        if(sc==pc || pc=='.') dp[i][j] = dp[i-1][j-1]; // 最后一个字符可以匹配
        else if(pc=='*'){ // 最后字符虽然不匹配,但p是'*'仍有希望匹配
            // s的最后一个字符和p的倒数第二个字符可以匹配
            if(sc==p.charAt(j-2) || p.charAt(j-2)=='.'){
                // 此时p中"#*"可以匹配0个或1个或多个s的字符
                dp[i][j] = dp[i][j-2] || dp[i-1][j-1] || dp[i-1][j];
            // s的最后一个字符和p的倒数第二个字符也不匹配,彻底匹配失败
            }else dp[i][j] = dp[i][j-2];
        }
    }
}
return dp[sLen][pLen];

题42 连续子数组的最大和

链接: link

int[] dp = new int[nums.length]; // dp[i]表示到下标i的最大子数组的和
dp[0] = nums[0];
int res = nums[0];
for(int i=1; i<nums.length; i++){
    dp[i] = Math.max(nums[i], dp[i-1] + nums[i]);
    res = Math.max(res, dp[i]);
}
return res;

题46 把数字翻译成字符串

链接: link

String s = String.valueOf(num); // 转成字符串方便处理
int[] dp = new int[s.length() + 1]; // dp[i]表示从后往前到下标i的方法数
dp[s.length()] = 1; // 最后一位表示字符串为空时,1是推算出的
dp[s.length()-1] = 1; // 倒数第二位表示只有一个字符时只有一种方法
for(int i=s.length()-2; i>=0; i--){ // 从后往前遍历
    // 如果当前位能和后一位组成"25"以内的合法数字
    if(s.charAt(i)=='1' || (s.charAt(i)=='2' && s.charAt(i+1) < '6')){
        dp[i] = dp[i+1] + dp[i+2];
    }// 不能就和之前方法数一样
    else dp[i] = dp[i+1];
}
return dp[0];

题47 礼物的最大价值

链接: link

int m = grid.length, n = grid[0].length;
int[][] dp = new int[m][n]; // dp[i][j]表示到达下标(i,j)能得到的最大价值
dp[0][0] = grid[0][0]; // 初始化
for(int i=1; i<m; i++) dp[i][0] = dp[i-1][0] + grid[i][0];
for(int j=1; j<n; j++) dp[0][j] = dp[0][j-1] + grid[0][j];
for(int i=1; i<m; i++){
    for(int j=1; j<n; j++){
        dp[i][j] = grid[i][j] + Math.max(dp[i][j-1], dp[i-1][j]);
    }
}
return dp[m-1][n-1];

题60 n个骰子的点数

链接: link

double[] cur = new double[6]; // 只有1个骰子的结果
Arrays.fill(cur, 1 / 6.0); // 概率都是1/6
for(int i=2; i<=n; i++){ // 骰子数从2到n
    double[] next = new double[5*i+1]; // 增加一个骰子可能结果的个数
    for(int j=0; j<cur.length; j++){ // 遍历当前骰子的概率分布
        for(int k=0; k<6; k++){ // 当前骰子再掷出一个1~6
            next[j+k] += cur[j] / 6.0; // 当前概率值乘上1/6
        }
    }
    cur = next; // 将当前结果更新
}
return cur;

题63 股票的最大利润

链接: link

if(prices.length==0) return 0;
int[] dp = new int[2];
dp[0] = -prices[0]; // dp[0]表示当天持有股票的最大利润
dp[1] = 0;  // dp[1]表示当天不持有股票的最大利润
for(int i=1; i<prices.length; i++){
    dp[0] = Math.max(dp[0], -prices[i]); // 之前买入,或今天买入
    dp[1] = Math.max(dp[1], prices[i]+dp[0]); // 之前卖出,或今天卖出
}
return dp[1];
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值