剑指offer总结(16-26)

剑指offer(16-26)

前言

【代码的规范性、完整性、鲁棒性】

  1. 代码的基本功能(功能测试)
  2. 输入边界值是否能得到正确处理(边界测试)
  3. 能否判断输入是否合乎规范,并对不符合规范的输入予以合适的处理(负面测试)
16. 数值的整数次方

【题目】

给定一个double类型的浮点数baseint类型的整数exponent。求baseexponent次方。

保证baseexponent不同时为 0,不得使用库函数,同时不需要考虑大数问题

【思路】

注意:base = 0 的情况要处理,exponent 为负数时要另外处理

public class Solution {
    public double Power(double base, int exponent) {
        if(exponent == 0){
            return 1.0;  // 0的0次方在数学上无意义,为 0 或 1 都可以
        }
        if(exponent == 1){
            return base;
        }
        boolean isNegative = false;
        if(exponent < 0){
            if(base == 0.0){
                throw new RuntimeException();
            }
            isNegative = true;
            exponent = -exponent;
        }
        double pow = Power(base, exponent >> 1) * Power(base, exponent >> 1); // 位运算替代与2为底的运算
        if((exponent & 1) != 0){ // 与 1 & 判断 奇偶
            pow *= base;
        }
        return isNegative ? 1 / pow : pow;
  }
}
17. 打印从 1 到最大的 n 位数

【题目】

输入数字n,按顺序打印出从1到最大的n位十进制数。

比如输入3,则打印出1、2、3一直到最大的3位数即999.

【思路】大数问题用字符串或者char数组存储

由于 n 可能会非常大,因此不能直接用 int 表示数字,而是用char数组。

使用回溯法得到所有解。

result = []
def backtrack(已选择参数,待选择参数):
     可以需要将参数处理成已选择内容和待选择内容;

     排除不合法选择
    
     if 满足结束条件:
     	result.add(已选择内容)
     	return
    
     for 选择 in 待选择内容:
     	排除不合法选择
     	做选择,记录选择
        backtrack(已选择参数,待选择参数) //继续选择
		撤销选择,删除记录
public class Solution {
    public void print1ToMax(int n){
        if (n <= 0){
            return;
        }
        char[] number = new char[n];
    }
    
    private void backtrack(char[] number, int length){
        //结束条件
        if(length == number.length){
            printNumber(number);
        }
        for(int i = 0; i < 10; i ++){
            number[length] = (char) (i + '0');
            backtrack(number, length++);
        }
    }
    
    private void printNumber(char[] number){
        int index = 0;
        while(index < number.length && number[index] == '0'){
            index++;
        }
        while(index < number.length){
            System.out.print(number[index++]);
        }
        System.out.println();
    }
}
18.1 在 O(1) 时间内删除链表节点

【题目】

给定单链表的头指针和一个节点指针,定义一个函数在O(1)的时间内删除该节点

【思路】

  1. 如果该节点不是尾节点,则直接将下一个节点的值赋给该节点,然后令该节点的指向下下个节点,然后删除下个节点(O(1))
  2. 如果该节点没有下一个节点,则需要重头找到其上一个节点
  3. 假设要进行 n 次操作,第一种情况为 (n - 1) * 1,第二种为 n,平均为 (2n - 1)/ n,所以为 O(1)
public class Solution {
    public ListNode deleteNode(ListNode head, ListNode tobeDelete){
        //链表为空或只有一个节点
        if(head == null || tobeDelete == null || head.next == null){
            return null;
        }
        //有下一个节点
        if(tobeDelete.next != null){
            tobeDelete.value = tobeDelete.next.value;
            tobeDelete.next = tobedelete.next.next;
        }else {
            ListNode cur = head;
            while(cur.next != tobeDelete){
                cur = cur.next;
            }
            cur.next = tobeDelete.next
        }
        return head;
    }
}
18.2 删除链表中重复的结点

【题目】

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。

例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

【思路】

双指针辅助头

img

  1. 加一个辅助头节点(令其值不等于原头节点)
  2. 双指针
  3. q找变化点和终点;p与q之间判断变化点和终点前 q 的是否有重复值
/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        if (pHead == null || pHead.next == null){
            return pHead;
        }
        //辅助头节点
        ListNode head = new ListNode(pHead.val - 1); // 保证和第一个节点不相等;
        head.next = pHead;
        //双指针
        ListNode pre = head;
        ListNode cur = head.next;
        while(cur != null){
            while(cur.next != null && cur.val == cur.next.val){ // 寻找值的变化点 或 终点
                cur = cur.next;
            }
            if(pre.next == cur){ //变化点或终点前相等的 cur 只有一个,比较的不是值,而是对象地址
                pre = cur;
                cur = cur.next;
            }else {
                pre.next = cur.next;
                cur = pre.next;
            }
        }
        return head.next; //记得去掉辅助头节点
    }
}
19. 正则表达式匹配

【题目】

请实现一个函数用来匹配包括 ’ . ’ 和 ’ * ’ 的正则表达式。模式中的字符 ’ . ’ 表示任意一个字符,而 ’ * ’ 表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。

例如,字符串 “a a a” 与模式 “a . a” 和 “ab * a c * a” 匹配,但是与 “a a . a” 和 “a b * a” 均不匹配

【思路】

回溯法

匹配条件:两个都到结尾处

主要分成两种情况:

  1. 第二个字符是‘ * ’
    1. 第一个字符匹配或为‘ . ‘
      1. ’ * ‘ 表示 0
      2. ’ * ‘ 表示 1
      3. ’ * ‘ 表示多个
    2. 第一个字符不匹配
  2. 第二个字符不是 ‘ * ’
public class Solution {
    public boolean match(char[] str, char[] pattern)
    {
        if(str == null || pattern == null){
            return false;
        }
        return matchChar(str, pattern, 0, 0);
    }
    
    private boolean matchChar(char[] str, char[] pattern, int s, int p){
        //满足结束条件
        if(s >= str.length && p >= pattern.length){
            return true;
        }
        if(s < str.length && p >= pattern.length){
            return false;
        }
        
        //做选择 backtrack
        if((p + 1) < pattern.length && pattern[p + 1] == '*'){
            if(s < str.length && (str[s] == pattern[p] || pattern[p] == '.')){
                return (matchChar(str, pattern, s + 1, p + 2) || matchChar(str, pattern, s, p + 2) || matchChar(str, pattern, s + 1, p));
            }else{
                return matchChar(str, pattern, s, p + 2);
            }
        }else if(s < str.length && (str[s] == pattern[p] || pattern[p] == '.')){
            return matchChar(str, pattern, s + 1, p + 1);
        }else {
            return false;
        }
    }
}
20. 表示数值的字符串

【题目】

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。

例如,字符串 “+100”, “5e2”, “-123”, “3.1416” 和 “-1E-16” 都表示数值。 但是 “12e”, “1a3.14”, “1.2.3”, “±5” 和 “12e+4.3” 都不是。

【思路】

正则表达式

public class Solution {
    public boolean isNumeric(char[] str) {
        if(str == null){
            return false;
        }
        return new String(str).matches("[+-]?[0-9]*(\\.[0-9]*)?([eE][+-]?[0-9]+)?");
    }
}
21. 调整数组顺序使奇数位于偶数前面

【题目】

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

【思路】

不稳定:首尾双指针

稳定

//不稳定 O(N)+O(1)
public class Solution {
    public void reOrderArray(int [] array) {
        if(array == null || array.length == 0 || array.length == 1){
            return;
        }
        int begin = 0;
        int end = array.length - 1;
        while(begin < end){
            if(!isOdd(array[begin]) && isOdd(array[end])){
                swap(array, begin++, end--);
                continue;
            }else if (isOdd(array[begin])){
                begin++;
            }else if (!isOdd(array[end])){
                end--;
            }
        }
    }
    
    private boolean isOdd(int value){
        return (value & 1) == 1? true : false;
    }
    
    private void swap(int[] arr, int i, int j){
        arr[i] = arr[i] + arr[j];
        arr[j] = arr[i] - arr[j];
        arr[i] = arr[i] - arr[j];
    }
}

//稳定 O(N)+O(N)
public class Solution {
    public void reOrderArray(int [] array) {
        if(array == null || array.length < 2){
            return;
        }
        int[] res = new int[array.length];
        int begin = 0, end = array.length - 1;
        for(int i = 0, j = array.length - 1; i < array.length; i++, j--){
            if(isOdd(array[i])){
                res[begin++] = array[i];
            }
            if(!isOdd(array[j])){
                res[end--] = array[j];
            }
        }
        for(int i = 0; i < array.length; i++){
            array[i] = res[i];
        }
    }
    
    private boolean isOdd(int val){
        return (val & 1) == 1;
    }
}
22. 链表中倒数第 K 个结点

【题目】

输入一个链表,输出该链表中倒数第k个结点。

【思路】

双指针

//我的
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head == null || k == 0){ // 【错误】:没有考虑到倒数第0个
            return null;
        }
        ListNode p1 = head;
        ListNode p2 = head;
        for(int i = 1; i < k; i++){
            if(p2.next != null){
                p2 = p2.next;
            }else {
                return null;
            }
        }
        while(p2.next != null){
            p1 = p1.next;
            p2 = p2.next;
        }
        return p1;
    }
}
//简洁版
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        ListNode p = head, q = head;
        int i = 0;
        while(q != null){
            if(i >= k){
                p = p.next;
            }
            q = q.next;
            i++;
        }
        return i >= k ? p : null;
    }
}
23. 链表中环的入口结点

【题目】

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

【思路】

双指针:快慢指针

相遇则有环,将快指针指向头节点变慢指针,等待下一次相遇

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead == null){
            return null;
        }
        ListNode p1 = pHead, p2 = pHead;
        while(p2 != null && p2.next != null){
            p1 = p1.next;
            p2 = p2.next.next;
            if(p1 == p2){
                p2 = pHead;
                while(p1 != p2){
                    p1 = p1.next;
                    p2 = p2.next;
                }
                return p2;
            }
        }
        return null;
    }
}
24. 反转链表

【题目】

输入一个链表,反转链表后,输出新链表的表头。

【思路】

pre 记录前一个节点,初始化为 null,

cur 记录当前节点,初始化为head,

next 用于暂时存储cur的下一个

//单链表
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head == null){
            return head;}
        ListNode pre = null, cur = head, next = null;
        while(cur != null){
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}
//双链表
public class Solution{
    public ListNode ReverseBiList(ListNode head){
        ListNode pre = null, cur = head, next = null;
        while(cur != null){
            next = cur.next;
            cur.next = pre;
            cur.prior = next;
            pre = cur;
            cur = next;
        }
    }
}
25. 合并两个排序的链表

【题目】

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

【思路】

面对头节点不确定问题:辅助头节点

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null){
            return list2;
        }
        if(list2 == null){
            return list1;
        }
        ListNode preHead = new ListNode(-1);
        ListNode pre = preHead;
        while(list1 != null && list2 != null){
            if(list1.val <= list2.val){
                pre.next = list1;
                pre = pre.next;
                list1 = list1.next;
            }else{
                pre.next = list2;
                pre = pre.next;
                list2 = list2.next;
            }
        }
        while(list1 != null){
            pre.next = list1;
            break;  // 【错误处】:没有结束循环
        }
        while(list2 != null){
            pre.next = list2;
            break;
        }
        return preHead.next;
    }
}
25. 树的子结构

【题目】

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

【思路】

二叉树的问题一般都用递归求解

重点是确定最终成功与否的最终状态,即结束条件(递归出口)

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if(root1 == null || root2 == null) return false;
        return doesTree1HasTree2(root1, root2);
    }
    
    private boolean doesTree1HasTree2(TreeNode root1, TreeNode root2){
        //满足结束条件
        if(root2 == null){
            return true;
        }
        if(root1 == null){
            return false;
        }
        
        // 做选择
        if(root2.val == root1.val){
            if (doesTree1HasTree2(root1.left, root2.left) && doesTree1HasTree2(root1.right, root2.right)){
                return true;   //【错误点】:直接return 结果,显示出错,用if正确
            }
        }
        return (doesTree1HasTree2(root1.left, root2) || doesTree1HasTree2(root1.right, root2));
    }
}

1, root2);
}

private boolean doesTree1HasTree2(TreeNode root1, TreeNode root2){
    //满足结束条件
    if(root2 == null){
        return true;
    }
    if(root1 == null){
        return false;
    }
    
    // 做选择
    if(root2.val == root1.val){
        if (doesTree1HasTree2(root1.left, root2.left) && doesTree1HasTree2(root1.right, root2.right)){
            return true;   //【错误点】:直接return 结果,显示出错,用if正确
        }
    }
    return (doesTree1HasTree2(root1.left, root2) || doesTree1HasTree2(root1.right, root2));
}

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值