剑指offer的每题求解思路

本文详细解析了数组中重复数字的查找、二维数组中的查找、字符串空格替换、从尾到头打印链表、重建二叉树、用两个栈实现队列、斐波那契数列、青蛙跳台阶、旋转数组的最小数字、矩阵路径、机器人运动范围、减绳子问题、二进制中1的个数、数值的整数次方、打印数位数以及删除链表节点等算法问题,涉及动态规划、二分查找、深度优先搜索等核心算法思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

剑指offer03 数组中重复的数字

在这里插入图片描述
思路: 只要数组中的数字是有范围的,而且是与数组长度大小是有关系的,这时脑子中的第一思路就是用数组来模拟哈希表,简称数组hash,数组hash就是这个数组中的下标值就是原数组中的值

剑指offer04 二维数组中查找

在这里插入图片描述

思路:找到最后一列的值,从上到下进行比较,如果target小于这个值,说明,target在以这个值为右下角的矩形当中。如果target大于这个值,说明target在以这个值为右下角的矩形的下面!!!

剑指offer05 替换空格

在这里插入图片描述
思路:遍历s字符串,遇到空格,就append("%20"),很简单的一道题

剑指offer06 从尾到头打印链表

在这里插入图片描述
思路: 使用递归,借用系统栈,即可。

class Solution {
    int[] res;
    int len=0;
    int index=0;
    public int[] reversePrint(ListNode head) {
        if(head==null){
            len = index;
            return new int[len];
        }
        index++;
        res = reversePrint(head.next);
        index--;
        res[len-index-1] = head.val;
        return res;
    }
}

剑指offer07 重建二叉树

在这里插入图片描述
思路: 关键是从先序遍历的第一个节点是根节点,然后中序遍历中这个第一个节点前面的所有节点都是这个根节点的左子树中的节点,这样就可以递归了

Arrays.copyOfRange这个方法的返回值是一个重新的数组,而且是按照索引,且顾头不顾尾,右边的那个索引值不会取到

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int n = preorder.length;
        if (n == 0)
            return null;
        int rootVal = preorder[0], rootIndex = 0;
        for (int i = 0; i < n; i++) {
            if (inorder[i] == rootVal) {
                rootIndex = i;
                break;
            }
        }
        TreeNode root = new TreeNode(rootVal);// 注意注意Arrays.copyOfRange是以索引,而且顾头不顾尾,右边的那个索引值不取
        root.left = buildTree(Arrays.copyOfRange(preorder, 1, 1 + rootIndex), Arrays.copyOfRange(inorder, 0, rootIndex));
        root.right = buildTree(Arrays.copyOfRange(preorder, 1 + rootIndex, n), Arrays.copyOfRange(inorder, rootIndex + 1, n));
        return root;
    }
}

剑指offer09 用两个栈实现队列

思路:就想用两个杯子,互相倒开水,每次取数,你都要把杯子中的水全部倒到另外一个杯子中

class CQueue {
    LinkedList<Integer> stack1;
	LinkedList<Integer> stack2;
	public CQueue() {
		stack1 = new LinkedList<>();
		stack2 = new LinkedList<>();
	}
    // pushLast
	public void appendTail(int value) {
		stack1.add(value);
	}
    // getFirst
	public int deleteHead() {
		if (stack2.isEmpty()) {
			if (stack1.isEmpty()) return -1;
			while (!stack1.isEmpty()) {
				stack2.add(stack1.pop());
			}
			return stack2.pop();
		} 
        else
          return stack2.pop();
	}
}

剑指offer10-I 斐波那且数列

思路:最简单的动态规划,不说了。。。

剑指offer10-II 青蛙跳台阶

思路:非常简单的动态规划,dp[n] = dp[n-1] + dp[n-2]

剑指offer11 旋转数组的最小数字

在这里插入图片描述
关于二分查找的几个注意点,请看我这篇博客
俺的二分查找博客

class Solution {
    public int minArray(int[] numbers) {
        int len = numbers.length;
        int left = 0;
        int right=len-1;
        while(left<right){
            int mid = left + (right-left)/2;
            if(numbers[mid]<numbers[right]){
                right = mid;
            }
            else if(numbers[mid]==numbers[right]){
                right--;
            }
            else if(numbers[mid]>numbers[right]){
                left = mid + 1;
            }
        }
        return numbers[left];
    }
}

剑指offer12、13矩阵中的路径,机器人的运动范围

在这里插入图片描述在这里插入图片描述
思路: 都是深度优先索引,这个代码相比于动态规划,真是多了好几倍,不过理解起来却比动态规划简单好几倍。典型的纸老虎

class Solution {
    public boolean exist(char[][] board, String word) {
        if (board == null || board[0] == null || board.length == 0 || board[0].length == 0 || word == null || word.equals("")) {
            return false;
        }
        boolean[][] isVisited = new boolean[board.length][board[0].length];
        char[] chs = word.toCharArray();

        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (board[i][j] == chs[0]) {
                    if (dfs(board, i, j, isVisited, chs, 0)) return true;
                }
            }
        }
        return false;
    }

    private boolean dfs(char[][] board, int i, int j, boolean[][] isVisited, char[] chs, int index) {

        if (index == chs.length) {
            return true;
        }
        if (i < 0 || j < 0 || i == board.length || j == board[0].length || isVisited[i][j] || board[i][j] != chs[index]) {
            return false;
        }
        isVisited[i][j] = true;
        boolean res = dfs(board, i + 1, j, isVisited, chs, index + 1)
                || dfs(board, i - 1, j, isVisited, chs, index + 1)
                || dfs(board, i, j + 1, isVisited, chs, index + 1)
                || dfs(board, i, j - 1, isVisited, chs, index + 1);
        isVisited[i][j] = false;
        return res;
    }
}

机器人:

class Solution {
    public int movingCount(int m, int n, int k) {
        boolean[][] visited = new boolean[m][n];
        return dfs(0, 0, m, n, k, visited);
    }

    private int dfs(int i, int j, int m, int n, int k, boolean visited[][]) {
        if (i < 0 || i >= m || j < 0 || j >= n || (i/10 + i%10 + j/10 + j%10) > k || visited[i][j]) {
            return 0;
        }
        visited[i][j] = true;
        return dfs(i + 1, j, m, n, k, visited) + dfs(i - 1, j, m, n, k, visited) + 
               dfs(i, j + 1, m, n, k, visited) + dfs(i, j - 1, m, n, k, visited) + 1;
    }
}

两者都是在一个方格中进行DFS,其实关于dfs的代码,主要就是参数比较多,但是参数其实也是类似的,都要当前在哪个点的横纵坐标,都有方格的长与宽!!!以及visited[ ]数组

剑指offer14-I 减绳子

在这里插入图片描述
思路:常规的动态规划,dp[i] 表示整数i,拆分后得到的乘积的最大值,则求dp[n] 必须把n之前的数字都遍历一遍,都拆分看看,前面一部分就看做是已经拆分好的的最大值,后面一部分就单纯的是这一部分到末尾的距离
在这里插入图片描述
但这个dp[i]也要可能比i小,因此,还要比较一下

class Solution {
    public int integerBreak(int n) {
        int[] dp = new int[n+1];
        dp[1]=1;
        for(int i=2;i<=n;i++){
            for(int j=1;j<i;j++){
                dp[i] = Math.max(dp[i], Math.max(dp[j],j)*(i-j));
            }
        }
        return dp[n];
    }
}

减绳子II (跟上面的题目差不多,只不过有大数)

3在这里插入图片描述

思路:用贪心算法。尽可能多的分出长度为3的字段出来,这样得到的乘积就会最大。证明就省略了,记住这个结论即可

class Solution {
    public int cuttingRope(int n) {
        if(n < 4){
            return n - 1;
        }
        if(n==4) return 4;
        long res = 1;
        while(n > 4){
            res  = res * 3 % 1000000007;
            n -= 3;
        }
        return (int) (res * n % 1000000007);
    }
}

剑指offer15 二进制中1的个数

在这里插入图片描述

思路:这里考查 n & (n-1) 这个位运算的含义。即将二进制最后一个1变成0
public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int count=0;
        while(n!=0){
            count++;
            n = n & (n-1);
        }
        return count;
    }
}

剑指offer16 数值的整数次方

在这里插入图片描述

思路:简单递归
class Solution {
    public double myPow(double x, int n) {
        if(n==1) return x;
        if(n==0) return 1;
        if(n==-1) return 1/x;
        double half = myPow(x, n/2);
        double mod = myPow(x, n%2);
        return half * half * mod;
    }
}

剑指offer17 打印1到最大的n位数

在这里插入图片描述

思路:在赋值的时候可以一边从前赋值,一边从后赋值
class Solution {
    public int[] printNumbers(int n) {
         // x = 10的n次方 得到数组长度
        int x = (int)Math.pow(10,n);
        // 创建一个大小为10的n次方-1的数组
        int[] result = new int[x-1];
        for(int i=0,j=x-2;i<x-1;i++,j--){
            result[i] = i+1;
            result[j] = j+1;
            if(i==j) break;
        }
        return result; 
    }
}

剑指Offer18 删除链表的节点

在这里插入图片描述

用双指针:pre cur两个指针,pre指针在cur指针的前面,cur指针用来val节点,pre指针用来重指向
用递归
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        // 递归解法
        if(head==null) return null;
        if(head.val == val) return head.next;
        head.next = deleteNode(head.next, val);
        return head;
        
		// 双指针解法
        /*
        if(head == null) return head;
        ListNode cur = head;
        ListNode pre = null;
        if(cur.val == val) return head.next;
        while(cur.val != val) {
            pre = cur;
            cur = cur.next;
        }=
        pre.next = pre.next.next;
        return head;
        */
    }
}

剑指offer21 调整数组顺序使得奇数位于偶数前面

在这里插入图片描述

思路:有点快速排序的思想,定义双指针,一个从前面,一个从后面,前后夹击,前面定位到偶数,后面定位到奇数,然后就可以进行交换
class Solution {
    public int[] exchange(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        // 前后夹击,太秒了
        while (left < right) {
            // 从前往后定位到不是奇数的位置
            while (left < right && nums[left] % 2 != 0) {
                left++;
            }
            // 从后往前定位到不是偶数的位置
            while (left < right && nums[right] % 2 == 0) {
                right--;
            }
            // 将上面定位到的两个位置的数字进行交换
            if (left < right) {
                int temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
            }
        }
        return nums;
    }
}

剑指offer22 链表中倒数第k个节点

在这里插入图片描述

思路:使用快慢指针,使得快指针领先满指针k个节点,这样快指针到头之后,慢指针就是指向倒数第k个

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode fast = head;
        while(fast!=null) {
        	fast = fast.next;
        	if(k==0) {
        		head = head.next;
        	}else {
        		k--;
        	}
        }
        return head; 
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值