3rd刷剑指offer,问题汇总

1.剑指 Offer 14- II. 剪绳子 II,这道题目关于求余算法,还没有掌握。

2.剑指 Offer 16. 数值的整数次方 这道题目前也没有很好掌握

2020.11.10学习:这道题用递归比较好理解,代码如下:

关键的问题是当 n == Integer.MIN_VALUE时,-n == n,即 -1 * -2,147,483,648 == 2,147,483,648 == -2,147,483,648,为什么会这样呢,因为int(Integer)类型的取值范围是

\[{\rm{[ - }}{{\rm{2}}^{{\rm{31}}}},{2^{31}} - 1] = [ - 2,147,483,648,2,147,483,647]\]

很明显,-n此时比Integer.MAX_VALUE还要大一,所以数据溢出了,-n==n。解决方法是手动提取出一个1/x,然后让指数部分-1,即可。

class Solution {
    public double myPow(double x, int n) {
        if (n == 0) return 1;
        if (n < 0) return 1 / x * myPow(1 / x, -n - 1);
        return ((n & 1) == 0) ? myPow(x * x, n / 2) : x * myPow(x * x, n / 2);
    }
}

3.剑指 Offer 17. 打印从1到最大的n位数

这道题目虽然是简单题目,但是如果稍微改变条件,即不限制每位数字都在int范围内,即出现大数的情况,就不是那么简单能够写出来的了。大数问题要用String.

放一个题解:https://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/solution/mian-shi-ti-17-da-yin-cong-1-dao-zui-da-de-n-wei-2/   

2020.11.11学习:说是用到了全排列的算法,主要还是要理解这里用到的递归,并且递归外面套一层循环。

还要注意对001,002,011,这类高位是0的数值的截断细节,用到了nine和start辅助变量来实现。

贴个代码:
今天学习中,我对nine--那里还有一点没理清,回溯

class Solution {
    int[] res;
    int nine = 0, count = 0, start, n;
    char[] num, loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
    public int[] printNumbers(int n) {
        this.n = n;
        res = new int[(int)Math.pow(10, n) - 1];
        num = new char[n];
        start = n - 1;
        dfs(0);
        return res;
    }
    void dfs(int x) {
        if(x == n) {
            String s = String.valueOf(num).substring(start);
            if(!s.equals("0")) res[count++] = Integer.parseInt(s);
            if(n - start == nine) start--;
            return;
        }
        for(char i : loop) {
            if(i == '9') nine++;
            num[x] = i;
            dfs(x + 1);
        }
        nine--;
    }
}

4.剑指 Offer 27. 二叉树的镜像

2020/11/14 18:30

这道题目虽然标题为简单,虽然我目前会做了(然而并没有百分百掌握理解),但是这道题目我觉得很有意义,值得特别记录一下。

对于二叉树的题目,目前为止我认为有有两种形式的方法很适合,一是递归(二叉树的前后中遍历);二是队列(层序遍历)

而这道题目,考察的正是二叉树的遍历,前后中,层序。直接上代码吧

后续遍历,从下至上,从左至右,交换:

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if (root == null) return root;
        recur(root);
        return root;
    }
    private void recur(TreeNode root) {
        //后续遍历,即在最底层开始交换,并逐渐向上走
        if (root == null) return;
        recur(root.left);
        recur(root.right);
        TreeNode tmp = root.left;
        root.left = root.right;
        root.right = tmp;
    }
}

采用队列的方式,从上至下,从左向右交换:

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if (root == null) return null;
        LinkedList<TreeNode> queue = new LinkedList<>() {{add(root);}};
        while (!queue.isEmpty()) {
            TreeNode ans = queue.removeFirst();
            if (ans.left != null) queue.addLast(ans.left);
            if (ans.right != null) queue.addLast(ans.right);
            TreeNode tmp = ans.left;
            ans.left = ans.right;
            ans.right = tmp;
        }
        return root;
    }
}

 前序遍历,从上向下,从左至右,交换:

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if (root == null) {
            return null;
        }
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
        mirrorTree(root.left);
        mirrorTree(root.right);
        return root;
    }
}

中序遍历交换应该不行,递归之后没有变化,因为左边的交换之后,右边又会交换一遍,等于没交换。

5.剑指 Offer 30. 包含min函数的栈

这道题目需要注意的一点就是,Integer类型,判断是 == 和 equals()的区别

像Integer类型,当需要判断A和B的数值是否相等时,最好使用A.equals(B),因为这个方法是纯粹比较数值大小的,源码如下:

public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

具体可以看下面这篇博客: 

https://cloud.tencent.com/developer/article/1688593

6.剑指 Offer 31. 栈的压入、弹出序列

模拟栈的入栈和出栈即可。

7.剑指 Offer 33. 二叉搜索树的后序遍历序列

这道题我知道大致的思路,但是2020/11/18号来做,竟然提交老有错误,值得好好记录一番

class Solution {
    boolean ans = true;
    public boolean verifyPostorder(int[] postorder) {
        if (postorder == null || postorder.length < 1) return ans;
        recur(postorder, 0, postorder.length - 1);
        return ans;
    }
    private void recur(int[] postorder, int low, int high) {
        if (low >= high) return;
        //对于index的情况,针对[5,2,-17,-11,25]的情形,i <= high 才行
        int rootVal = postorder[high], index = -1;
        for (int i = 0; i <= high; i++) {
            if (postorder[i] >= rootVal) {
                index = i;
                break;
            }
        }
        for (int i = index + 1; i < high; i++) {
            if (postorder[i] < rootVal) {
                ans = false;
                return;
            }
        }
        recur(postorder, low, index - 1);
        recur(postorder, index, high - 1);
    }
}

8.剑指 Offer 34. 二叉树中和为某一值的路径

2020/11/18,靠,回溯又不太熟了。其实大体思路还是对的,但是没注意到list的回溯时,改变list种的指,ans中list对应的值也对应改变了。

在ans添加满足答案的list前,要使用ArrayList的构造函数(Collections c)新建一个ArrayList,否则在后续的改动中,ans中的list的值也会发生变化。

另外可以使用(sum + root.val)的形式去传值,这样就不用在返回阶段,手动的再去sum - root.val的值了。

另外使用LinkedList插入删除数据比ArrayList快很多,LInkedlist既可以当作List,也可以当作Queue,Deque

class Solution {
    List<List<Integer>> ans = new ArrayList<>();;
    int target;
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        if (root == null) return ans;
        ans = new ArrayList<>();
        target = sum;
        LinkedList<Integer> list = new LinkedList<>();
        recur(root, list, 0);
        return ans; 
    }
    private void recur(TreeNode root, LinkedList<Integer> list, int sum) {
        if (root == null) return;
        if (root.left == null && root.right == null) {
            if (sum + root.val == target) {
                list.add(root.val);
                ans.add(new ArrayList<>(list));
                list.removeLast();
            }
            return;
        }
        list.add(root.val);
        recur(root.left, list, sum + root.val);
        recur(root.right, list, sum + root.val);
        list.removeLast();
    }
}

9.剑指 Offer 36. 二叉搜索树与双向链表

又是大体知道思路,但是实现细节上出了问题。

2020/11/19,我的想法是利用后续遍历,修改左子节点的右指向,修改右子节点的左指向。同时用类变量记录第一个节点和最后一个节点,在主程序中实现头结点和尾节点的连接。

这种代码如下:

class Solution {
    Node pre, tail;
    int count;
    public Node treeToDoublyList(Node root) {
        if (root == null) return null;
        recur(root);
        pre.left = tail;
        tail.right = pre;
        return pre;
    }
    private void recur(Node root) {
        if (root == null) return;
        if (root.left == null && root.right == null) {
            if (count++ == 0) pre = root;
            return;
        }
        recur(root.left);
        recur(root.right);
        if (root.left != null) root.left.right = root;
        if (root.right != null) {
            root.right.left = root;
            tail = root.right;
        }
    }
}

但是对于下面这颗树,我的输出是,漏了节点3,不知道是哪出了问题!我初步估计是在tail的赋值过程。

2020/11/20 其实一开始我的大方向变了,跟tail的赋值过程没有关系。

这道题目是关于二叉搜索树的升序,所以应该用中序遍历,因为二叉搜索树的中序遍历就是节点值的升序排列。

巧妙的利用了head指针和pre指针,来构建双向链表以及头结点和尾节点的捕捉,便于在主函数中实现头尾相连。具体上代码吧,非常巧妙

class Solution {
    Node head, pre;
    public Node treeToDoublyList(Node root) {
        if (root == null) return null;
        recur(root);
        head.left = pre;
        pre.right = head;
        return head;
    }
    private void recur(Node root) {
        if (root == null) return;
        recur(root.left);
        if (pre != null) pre.right = root;
        else head = root;
        root.left = pre;
        pre = root;
        recur(root.right);
    }
}

10剑指 Offer 37. 序列化二叉树

2020/11/20 这道题目到没什么难点,用好层序遍历和队列即可。

新收获:StringBuilder()的对象sb.append(root.val + ",") ;时应该改写成sb.append(root.val).append(",");可大幅减少耗时

11剑指 Offer 38. 字符串的排列

2020/11/21 这种递归的题我目前愿称之为递归最难题,似乎是排列组合?有时间深入探讨一下这种类型的题目,和前面3.剑指 Offer 17. 打印从1到最大的n位数的算法有点像。还是多敲代码多理解吧

直接放代码:

class Solution {
    List<String> ans;
    char[] tmp;
    public String[] permutation(String s) {
        if (s == null || s.length() < 1) return new String[0];
        //这里可以思考一下ArrayList和LinkedList性能区别
        ans = new LinkedList<>();
        tmp = s.toCharArray();
        dfs(0);
        return ans.toArray(new String[ans.size()]);
    }
    private void dfs(int index) {
        if (index == tmp.length - 1) {
            ans.add(new String(tmp));
            return;
        }
        Set<Character> set = new HashSet<>();
        for (int i = index; i < tmp.length; i++) {
            if (set.contains(tmp[i])) continue;
            set.add(tmp[i]);
            swap(i, index);
            dfs(index + 1);
            swap(index, i);
        }
    }
    private void swap(int i, int j) {
        char t = tmp[i];
        tmp[i] = tmp[j];
        tmp[j] = t;
    }   
}

12剑指 Offer 42. 连续子数组的最大和

这道题目用到了滑动窗口,我理解的滑动窗口,就是利用上前面窗口计算过的信息进行后续计算,避免了重复计算,提高了效率。

思路:定一个滑动窗口,如果窗口内的值<=0时,就把滑动窗口重置,并把最左端放到当前计算数的右边一位。

Tips:通过比较求最大/最小值时,一般设定ans初始值为Integer.MIN_VALUE/Integer.MAX_VALUE 比较好。

代码如下:

class Solution {
    public int maxSubArray(int[] nums) {
        int ans = Integer.MIN_VALUE, i = 0, j = 0, tmp = 0;
        while (j < nums.length) {
            tmp += nums[j++];
            ans = Math.max(ans, tmp);
            if (tmp <= 0) {
                i = j;
                tmp = 0;
            }
        }
        return ans;
    }
}

也可以用动态规划,代码如下:

class Solution {
    public int maxSubArray(int[] nums) {
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int ans = dp[0];
        for (int i = 1; i < nums.length; i++) {
            dp[i] = nums[i];
            dp[i] += Math.max(dp[i - 1], 0);
            ans = Math.max(dp[i], ans);
        }
        return ans;
    }
}

13 剑指 Offer 46. 把数字翻译成字符串

2020/11/27 不会

动态规划

14 剑指 Offer 48. 最长不含重复字符的子字符串

2020/11/28 不会

15 剑指 Offer 53 - II. 0~n-1中缺失的数字

2020/12/1 不会 尝试先找到一个中间数字,然后向两端扩散,若数字不连续,则返回当前值+1的数值,但是对于特殊情况,例如[0]应返回1,[1]应该返回0,还不能得出正确结果。面试题53 - II. 0~n-1 中缺失的数字(二分法,清晰图解) - 0~n-1中缺失的数字 - 力扣(LeetCode) (leetcode-cn.com)

16 剑指 Offer 57 - II. 和为s的连续正数序列

这道题目用到了滑动窗口的方法,知道了方法,重要的是具体的代码实现。

此题还有一个非常重要知识点,如何将一个List<List<Integer>>快速转换成一个int[][]数组

2020/12/5 我利用了List<List<Integer>>,还有一个内部的LinkedList来记录数据,最后能返回正确结果,但是还不知道怎么转换成二维数组,代码如下:

    public List<List<Integer>> findContinuousSequence(int target) {
        List<List<Integer>> ans = new ArrayList<>();
        LinkedList<Integer> tmp = new LinkedList<>();
        int low = 1, high = 1, sum = 1;
        tmp.add(1);
        while (high <= (target + 1) / 2) {
            if (sum < target) {
                high++;
                sum += high;
                tmp.add(high);
            } else if (sum > target) {
                tmp.removeFirst();
                sum -= low;
                low++;
            } else {
                ans.add(new ArrayList<>(tmp));
                low++;
                high++;
                tmp.add(high);
                sum += high - tmp.removeFirst();
            }
        }
        return ans;
    }

更简便的代码如下所示:可以看出,List<int[]>可以非常简单的利用toArray()方法转化为二维数组。

class Solution {
    public int[][] findContinuousSequence(int target) {
        List<int[]> vec = new ArrayList<int[]>();
        for (int l = 1, r = 2; l < r;) {
            int sum = (l + r) * (r - l + 1) / 2;
            if (sum == target) {
                int[] res = new int[r - l + 1];
                for (int i = l; i <= r; ++i) {
                    res[i - l] = i;
                }
                vec.add(res);
                l++;
            } else if (sum < target) {
                r++;
            } else {
                l++;
            }
        }
        return vec.toArray(new int[vec.size()][]);
    }
}

2020/12/6 贴一个我自己写的,与上面的代码,判断条件有所不同

class Solution {
    public int[][] findContinuousSequence(int target) {
        List<int[]> ans = new ArrayList<>();
        if (target < 3) return new int[0][0];
        int low = 1, high = 2;
        while (low < high && high <= (target + 1) / 2) {
            int sum = (low + high) * (high + 1 - low) / 2;
            if (sum == target) {
                int[] tmp = new int[high + 1 - low];
                for (int i = low; i <= high; i++) {
                    tmp[i - low] = i;
                }
                ans.add(tmp);
                low++;
                high++;
            } else if (sum < target) {
                high++;
            } else {
                low++;
            }
        }
        return ans.toArray(new int[ans.size()][]);
    }
}

17 剑指 Offer 60. n个骰子的点数

2020/12/9 这道题目我知道要用动态规划做,关键点在于求出每个点数对应出现的频率。这次我用的是一维数组,但是出错了,emm代码已经被我覆盖了。

我的思路就是,

但是这种思路只能正确求解n = 2····

所以说动态规划,还是要和前面的dp数值联系起来,这种思路,与前面的dp,没有联系。

正确的思路代码应该是这样

    public double[] twoSum(int n) {
        //先确定n个筛子,有多少种可能性 n ~ 6n,所以个数为5n + 1
        int[][] dp = new int[n + 1][6 * n + 1];//我喜欢用直接的下标来表示,所以需要+1
        for (int i = 1; i <= 6; i++) dp[1][i] = 1;//给一个筛子的情况初始化
        for (int i = 2; i <= n; i++) {
            for (int j = i; j <= i * 6; j++) {
                int check = j > 6 ? 6 : j - 1;
                for (int k = 1; k <= check; k++) {
                    dp[i][j] += dp[i - 1][j - k];
                }
            }
        }
        double[] ans = new double[5 * n + 1];
        double fen_mu = Math.pow(6, n);
        for (int i = n; i <= 6 * n; i++) {
            ans[i - n] = dp[n][i] / fen_mu;
        }
        return ans;
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值