力扣 (LeetCode) 剑指 Offer(第 2 版)刷题(java)合集-02

剑指 Offer 16. 数值的整数次方

难度中等163

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,x^n)。不得使用库函数,同时不需要考虑大数问题。

示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:

输入:x = 2.10000, n = 3
输出:9.26100

示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2**-2 = 1/2**2 = 1/4 = 0.25

提示:

  • -100.0 < x < 100.0
  • -231 <= n <= 231-1
  • -104 <= xn <= 104

思路一:递归

递归是比较好理解的

如果n == 0,返回1
如果n < 0,最终结果为 1/x^{-n}

如果n为奇数,最终结果为 x * x ^ {n - 1}

如果n为偶数,最终结果为 x ^ {2*(n/2)}

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

注:Java中因为n的最小值可以取到 Integer.MIN_VALUE,如果直接取它的相反数的话还是它自己,会导致堆栈溢出,因此提一个x出来,具体看代码——避免溢出

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

难度简单128

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

示例 1:

输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]

说明:

  • 用返回一个整数列表来代替打印
  • n 为正整数

这里如果题目中说明的是返回整数数组,那么以下的方法就有效

class Solution {
    public int[] printNumbers(int n) {
        int end = (int)Math.pow(10, n) - 1;
        int[] res = new int[end];
        for(int i = 0; i < end; i++)
            res[i] = i + 1;
        return res;
    }
}

如果题目是返回的是字符串数组,如果考点是大数越界情况下的打印。需要解决以下三个问题:

  1. 表示大数的变量类型:
    无论是 short / int / long … 任意变量类型,数字的取值范围都是有限的。因此,大数的表示应用字符串 String 类型。

  2. 生成数字的字符串集:
    使用 int 类型时,每轮可通过 +1生成下个数字,而此方法无法应用至 String 类型。并且, String 类型的数字的进位操作效率较低,例如 “9999” 至 “10000” 需要从个位到千位循环判断,进位 4 次。

    观察可知,生成的列表实际上是 n 位 00 - 99 的 全排列 ,因此可避开进位操作,通过递归生成数字的 String 列表。

  3. 递归生成全排列:
    基于分治算法的思想,先固定高位,向低位递归,当个位已被固定时,添加数字的字符串。例如当 n = 2 时(数字范围 1 - 99 ),固定十位为 00 - 99 ,按顺序依次开启递归,固定个位 0 - 9 ,终止递归并添加数字字符串。
    img
    下面的递归迭代具备代表性意义,这类题目多多练手理解深刻!!!!

class Solution {
    StringBuilder res;
    int count = 0, n;
    char[] num, loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
    public String printNumbers(int n) {
        this.n = n;
        res = new StringBuilder(); // 数字字符串集
        num = new char[n]; // 定义长度为 n 的字符列表
        dfs(0); // 开启全排列递归
        res.deleteCharAt(res.length() - 1); // 删除最后多余的逗号
        return res.toString(); // 转化为字符串并返回
    }
    void dfs(int x) {
        if(x == n) { // 终止条件:已固定完所有位
            res.append(String.valueOf(num) + ","); // 拼接num 并添加至 res 尾部,使用逗号隔开
            return;
        }
        for(char i : loop) { // 遍历 ‘0‘ - ’9‘
            num[x] = i; // 固定第 x 位为 i
            dfs(x + 1); // 开启固定第 x + 1 位
        }
    }
}

为了避免数字开头出现0,先把首位first固定,first取值范围为1~9
用digit表示要生成的数字的位数,本题要从1位数一直生成到n位数,对每种数字的位数都生成一下首位,所以有个双重for循环
生成首位之后进入递归生成剩下的digit - 1位数,从0~9中取值
递归的中止条件为已经生成了digit位的数字,即index == digit,将此时的数num转为int加到结果res中

class Solution {
    int[] res;
    int count = 0;

    public int[] printNumbers(int n) {
        res = new int[(int)Math.pow(10, n) - 1];
        for(int digit = 1; digit < n + 1; digit++){
            for(char first = '1'; first <= '9'; first++){
                char[] num = new char[digit];
                num[0] = first;
                dfs(1, num, digit);
            }
        }
        return res;
    }

    private void dfs(int index, char[] num, int digit){
        if(index == digit){
            res[count++] = Integer.parseInt(String.valueOf(num));
            return;
        }
        for(char i = '0'; i <= '9'; i++){
            num[index] = i;
            dfs(index + 1, num, digit);
        }
    }
}

解法:以下代码来自班上小哥哥,也是大数解法,写的很好

public String printNumbers(int n) {
        StringBuilder cache=new StringBuilder();
        List<String> ans=new ArrayList<>();
        int i=0;
        BIGCore(cache,ans,0,n);
        StringBuilder stringBuilder=new StringBuilder();
        for (String a:ans) 
        {
            stringBuilder.append(a+",");
        }
        stringBuilder.deleteCharAt(stringBuilder.length()-1);
    return stringBuilder.toString();
    }
    
public void BIGCore(StringBuilder cache, List<String> ans, int i, int n){
        if (i==n) {
            if (cache.length() > 0)
                ans.add(cache.toString());
        }
        else for (int num = 0;num<=9;num++){
            if (num!=0 || cache.length()>0) {
                cache.append(num);
                BIGCore(cache, ans, i + 1, n);
                cache.deleteCharAt(cache.length()-1);
            }
            else
                BIGCore(cache, ans, i + 1, n);
        }

上面的代码,思想都是迭代,一种是利用char[]数组,每一轮迭代时进行替换,后一种就是利用SringBuilder,在每一轮迭代时先delete最后一个元素,再进行append。

在这里迭代的思想可以好好参悟,一起加油吧!

剑指 Offer 18. 删除链表的节点

难度简单

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

**注意:**此题对比原题有改动

示例 1:

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

示例 2:

输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

说明:

  • 题目保证链表中节点的值互不相同
  • 若使用 C 或 C++ 语言,你不需要 freedelete 被删除的节点

解法一:单节点

  • 下面的代码仅仅区分了头节点,头结点以后都是head.next节点,故一并考虑,解法更胜一筹
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if (head == null) return null;
        if (head.val == val) return head.next;
        ListNode cur = head;
        while (cur.next != null && cur.next.val != val)
            cur = cur.next;
        if (cur.next != null)
            cur.next = cur.next.next;
        return head;
    }
}

推荐解法二:双节点(前后驱节点):

本题删除值为 val 的节点分需为两步:定位节点、修改引用。

定位节点: 遍历链表,直到 head.val == val 时跳出,即可定位目标节点。
修改引用: 设节点 cur 的前驱节点为 pre ,后继节点为 cur.next ;则执行 pre.next = cur.next ,即可实现删除 cur 节点。

Picture1.png

算法流程:
特例处理(算法题中的边界条件,是做一道算法题首先需要考虑的,希望谨慎): 当应删除头节点 head 时,直接返回 head.next 即可。
初始化: pre = head , cur = head.next 。
这里pre是前节点,cur是后节点。
定位节点: 当 cur 为空 或 cur 节点值等于 val 时跳出。
保存当前节点索引,即 pre = cur 。
遍历下一节点,即 cur = cur.next 。
删除节点: 若 cur 指向某节点,则执行 pre.next = cur.next ;若 cur 指向 null,代表链表中不包含值为 val 的节点。
返回值: 返回链表头部节点 head 即可。

class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if(head.val == val) return head.next;
        ListNode pre = head, cur = head.next;
        while(cur != null && cur.val != val) {
            pre = cur;
            cur = cur.next;
        }
        if(cur != null) pre.next = cur.next;
        return head;
    }
}
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

示例:

输入:nums = [1,2,3,4]
输出:[1,3,2,4] 
注:[3,1,2,4] 也是正确的答案之一。

提示:

  1. 0 <= nums.length <= 50000
  2. 1 <= nums[i] <= 10000

解法一:首尾双指针
定义头指针 left,尾指针 right.
left 一直往右移,直到它指向的值为偶数
right 一直往左移, 直到它指向的值为奇数
交换 nums[left] 和 nums[right].
重复上述操作,直到 left == right。

bingo自己写的啦

public int[] exchange(int[] nums) {
    int i=0;
    int j=nums.length-1;
    int temp;
    while (i<j){
        while (i<j&&nums[i]%2==1)
            i++;
        while (i<j&&nums[j]%2==0)
            j--;
        temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
    return nums;
}

解法二:快慢双指针
定义快慢双指针 fast 和 low ,fast 在前, low 在后 .
fast的作用是向前搜索奇数位置,low 的作用是指向下一个奇数应当存放的位置
fast 向前移动,当它搜索到奇数时,将它和 nums[low] 交换,此时 low 向前移动一个位置 .
重复上述操作,直到 fast 指向数组末尾 .

img

C++的代码,不妨碍理解

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        int low = 0, fast = 0;
        while (fast < nums.size()) {
            if (nums[fast] & 1) {
                swap(nums[low], nums[fast]);
                low ++;
            }
            fast ++;
        }
        return nums;
    }
};
剑指 Offer 18. 删除链表的节点

难度简单

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

**注意:**此题对比原题有改动

示例 1:

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

示例 2:

输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

说明:

  • 题目保证链表中节点的值互不相同
  • 若使用 C 或 C++ 语言,你不需要 freedelete 被删除的节点

解法一:单节点

  • 下面的代码仅仅区分了头节点,头结点以后都是head.next节点,故一并考虑,解法更胜一筹
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if (head == null) return null;
        if (head.val == val) return head.next;
        ListNode cur = head;
        while (cur.next != null && cur.next.val != val)
            cur = cur.next;
        if (cur.next != null)
            cur.next = cur.next.next;
        return head;
    }
}

解法二:双节点):

本题删除值为 val 的节点分需为两步:定位节点、修改引用

1、定位节点: 遍历链表,直到 head.val == val 时跳出,即可定位目标节点。
2、修改引用: 设节点 cur 的前驱节点为 pre ,后继节点为 cur.next ;则执行 pre.next = cur.next ,即可实现删除 cur 节点。

Picture1.png

算法流程:
特例处理: 当应删除头节点 head 时,直接返回 head.next 即可。
初始化: pre = head , cur = head.next 。
定位节点: 当 cur 为空 或 cur 节点值等于 val 时跳出。
保存当前节点索引,即 pre = cur 。
遍历下一节点,即 cur = cur.next 。
删除节点: 若 cur 指向某节点,则执行 pre.next = cur.next ;若 cur 指向 null,代表链表中不包含值为 val 的节点。
返回值: 返回链表头部节点 head 即可。

class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if(head.val == val) return head.next;
        ListNode pre = head, cur = head.next;
        while(cur != null && cur.val != val) {
            pre = cur;
            cur = cur.next;
        }
        if(cur != null) pre.next = cur.next;
        return head;
    }
}
剑指 Offer 22. 链表中倒数第k个节点

难度简单

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.

解法:双指针

解题思路:
第一时间想到的解法:
先遍历统计链表长度,记为 n ;
设置一个指针走 (n-k) 步,即可找到链表倒数第 k 个节点。

使用双指针则可以不用统计链表长度。

算法流程:
初始化: 前指针 former 、后指针 latter ,双指针都指向头节点 head 。
构建双指针距离: 前指针 former 先向前走 k步(结束后,双指针 former 和 latter 间相距 k 步)。
双指针共同移动: 循环中,双指针 former 和 latter 每轮都向前走一步,直至 former 走过链表 尾节点 时跳出(跳出后, latter 与尾节点距离为 k-1,即 latter 指向倒数第 k 个节点)。
返回值: 返回 latter 即可。

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode former = head, latter = head;
        for(int i = 0; i < k; i++)
            former = former.next;
        while(former != null) {
            former = former.next;
            latter = latter.next;
        }
        return latter;
    }
}
//以下是自己写的,思考类似:
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode fast=head;
        ListNode low=head;
        int order=1;
        while (order<k){
            fast=fast.next;
            order++;
        }
        while (fast!=null){
            fast=fast.next;
            low=low.next;
        }
        return low;
    }
剑指 Offer 24. 反转链表

难度简单

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

限制:

0 <= 节点个数 <= 5000

思路一:迭代
迭代需要三个指针,pre,cur,tmp,分别按顺序指向三个节点
三个指针的初始化:
imgpre指向空节点,cur指向头结点head,tmp指向head.next,因为head.next可能不存在,tmp在循环中定义,这样如果head为空就不会进入循环
迭代过程
tmp指向cur.next
cur指向pre
pre移动到cur位置
cur移动到tmp位置
当cur为空时,返回pre

做了几次,结合图看,恍然大悟!!!

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur = head, pre = null;
        while(cur != null) {
            ListNode tmp = cur.next;  // 暂存后继节点 cur.next
            cur.next = pre;  // 修改 next 引用指向
            pre = cur;  // pre 暂存 cur
            cur = tmp;  // cur 访问下一节点
        }
        return pre;
    }
}
剑指 Offer 25. 合并两个排序的链表

难度简单124

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

限制:

0 <= 链表长度 <= 1000
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dum = new ListNode(0), cur = dum;
        while(l1 != null && l2 != null) {
            if(l1.val < l2.val) {
                cur.next = l1;
                l1 = l1.next;
            }
            else {
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        cur.next = l1 != null ? l1 : l2;
        return dum.next;
    }
}
剑指 Offer 26. 树的子结构

难度中等

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

例如:
给定的树 A:

​     3
​    / \
   4   5
  / \
 1   2`

给定的树 B:

4
 /
 1

返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

示例 1:

输入:A = [1,2,3], B = [3,1]
输出:false

示例 2:

输入:A = [3,4,5,1,2], B = [4,1]
输出:true

限制:

0 <= 节点个数 <= 10000

解题思路:
若树 B 是树 A 的子结构,则子结构的根节点可能为树 A 的任意一个节点。因此,判断树 B 是否是树 A 的子结构,需完成以下两步工作:

先序遍历树 A 中的每个节点 nA(对应函数 isSubStructure(A, B))
判断树 A 中 以 n A 为根节点的子树 是否包含树 B 。(对应函数 recur(A, B))

recur(A, B) 函数:

终止条件:
当节点 B 为空:说明树 B 已匹配完成(越过叶子节点),因此返回 true ;
当节点 A 为空:说明已经越过树 A 叶子节点,即匹配失败,返回false ;
当节点 A 和 B 的值不同:说明匹配失败,返回false ;
返回值:
判断 A 和 B 的左子节点是否相等,即 recur(A.left, B.left) ;
判断 A 和 B 的右子节点是否相等,即 recur(A.right, B.right) ;
isSubStructure(A, B) 函数:

特例处理: 当 树 A 为空 或 树 B 为空 时,直接返回false ;
返回值: 若树 B 是树 A 的子结构,则必满足以下三种情况之一,因此用或 || 连接;
以 节点 A 为根节点的子树 包含树 B ,对应 recur(A, B);
树 B 是 树 A 左子树 的子结构,对应 isSubStructure(A.left, B);
树 B 是 树 A 右子树 的子结构,对应 isSubStructure(A.right, B);
以上 2. 3. 实质上是在对树 A 做 先序遍历 。

class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B));
    }
    boolean recur(TreeNode A, TreeNode B) {
        if(B == null) return true;
        if(A == null || A.val != B.val) return false;
        return recur(A.left, B.left) && recur(A.right, B.right);
    }
}

这些代码来源于力扣官网和一些大神的解答,如若侵权,本人定删。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值