剑指offer刷题宝典--第1节

刷题重点总结

1.动态规划 难题
2.二叉树序列化

1、Collection接口常用方法

方法含义
1add(Object obj)
2addAll(Collection coll)
3size()
4clear()void clear():清空集合元素
5isEmpty()
6contains(Object obj)
7containsAll(Collection coll)
8remove(Object obj)
9removeAll(Collection coll)
10retainAll(Collection coll)boolean retainAll(Collection c): 把交集的结果存在当前集合中,不影响c
交集:获取当前集合和coll1集合的交集,并返回给当前集合
11equals(Object obj)boolean equals(Object obj)集合是否相等
想返回true,需要当前集合和形参集合的元素都相同。 【并且顺序要一致】
12hasCode()hashCode()获取集合对象的哈希值
13toArray()Object[] toArray()集合转成对象数组
14iterator()iterator(): 返回迭代器对象,用于集合遍历
15Collections.sort(list);

必须掌握上述方法!

2、list常用方法

增:add(Object obj)

插:add(int index, Object ele)

删:remove(int index) / remove(Object obj) 【注意必须是obj的类型!!】

ArrayList循环删除元素,要注意只能倒序删除,不能正序删除!否则会产生这种for each写法会发现报出著名的并发修改异常Java.util.ConcurrentModificationException。】

改:set(int index, Object ele)
查:get(int index)

长度:size()
遍历:

​ ① Iterator迭代器方式
​ ② 增强for循环
​ ③ 普通的循环

排序 list1.sort(Comparator c)

list.indexOf(cur)

3、数组排序

1、一维数组:

//方式一
ArrayList<Integer> list2 = new ArrayList<>();
Collections.sort(list2);
//方式二
list2.sort(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return 0;
    }
});

2、二维数组

//方式一
int[][] arr = new int[n][2];
Arrays.sort(arr, new Comparator<int[]>() {
    @Override
     public int compare(int[] o1, int[] o2) {
         if (o1[0] == o2[0]) return o1[1] - o2[1];
            return o1[0] - o2[0];
     }
});
//方式二
ArrayList<ArrayList<Integer>> list = new ArrayList<>();
list.sort(new Comparator<ArrayList<Integer>>() {
    @Override
    public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
        return 0;
    }
});

二维数组增加元素:

list.add(new ArrayList<>(Arrays.asList(arr[i][0], arr[i][1])));

3、list转成数组

  • 一维list转成一维数组
res.stream().mapToInt(x -> x).toArray();
  • 二维list转成二维数组
res.toArray(new int[res.size()]);

4、String类常用方法

int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白 【用户注册的时候,可以对输入的字符串进行去除空白操作】

boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString):比较两个字符串的大小


String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。

boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true 【参数:字符串】
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索

注:indexOf和lastIndexOf方法如果未找到都是返回-1


替换:
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。


匹配:
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。


切片:
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

参数2: limit匹配限制
limit的值 含义
大于0 最多匹配limit-1次,次数到了,最后面的作为一个整体返回,数组长度不会超过limit

5、StringBuffer、StringBuilder中的常用方法

增:append(xxx)
删:delete(int start,int end) 【凡是涉及开始和结束的索引,都是左闭右开!!】
改:setCharAt(int n ,char ch) 【修改一个字符】 / replace(int start, int end, String str)
查:charAt(int n )
插:insert(int offset, xxx)
长度:length()
遍历:for() + charAt() / toString()

反转:reverse()

6、Arrays工具类中常用的方法

Arrays:提供了很多操作数组的方法。

  1. boolean equals(int[] a,int[] b) 判断两个数组是否相等。

  2. String toString(int[] a) 输出数组信息。

  3. void fill(int[] a,int val) 将指定值填充到数组之中。【全部元素被替换成val】

  4. void sort(int[] a) 对数组进行排序。【原数组被改变了,只能是一维数组】

  5. int binarySearch(int[] a,int key) 对排序后的数组进行二分法检索指定的值。 【返回负数表示未找到指定值!!】

  6. Arrays.asList(num[k], num[i], num[j])) 该方法是将数组转化为list

  7. copyOfRange(int[] original,int from,int to)

    • original为原始的int型数组,from为开始角标值,to为终止角标值。(其中包括from角标,不包括to角标。即处于[from,to)状态)
  8. public static int[] copyOf(int[] original, int newLength)

    • 使用零复制指定的数组,截断或填充(如有必要),以使副本具有指定的长度。 对于在原始数组和副本中都有效的所有索引,这两个数组将包含相同的值。 对于在副本中有效但不在原件中有效的任何索引,副本将包含0。 当且仅当指定的长度大于原始数组的长度时,这些索引才会存在。

一、链表

JZ25 合并两个排序的链表

public class Solution {
    public ListNode Merge(ListNode list1, ListNode list2) {
        ListNode resList = new ListNode(-1);  // 返回的链表的头节点
        ListNode res = resList;  // 链表的尾节点,用于尾插入

        ListNode p = list1, q = list2;
        while (p != null && q != null) {
            if (p.val < q.val) {
                res.next = p;
                p = p.next;
            } else {
                res.next = q;
                q = q.next;
            }
            res = res.next;
        }
        //将剩余的链表拼在后面

//         if (p != null ) {
//             res.next = p;
//         }
        while (p != null) {
            res.next = p;
            p = p.next;
            res = res.next;
        }
        //直接连接指针
//         if (q != null) {
//             res.next = q;
//         }
        while (q != null) {
            res.next = q;
            q = q.next;
            res = res.next;
        }
        return resList.next;
    }
}

JZ52 两个链表的第一个公共结点

这道题的意思并不是判断结点值是否相等,而是判断结点是否相等,结点相等意味着结点值和结点地址都得相等才行

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p1=pHead1;
        ListNode p2=pHead2;
        while (p1 != p2) {
            p1=(p1==null)?pHead2:p1.next;
            p2=(p2==null)?pHead1:p2.next;
        }
        return p1;
    }
}

JZ23 链表中环的入口结点

要求:空间复杂度 O(1),时间复杂度 O(n)

方式一:快慢指针

如果有环,如何找到这个环的入口
此时我们已经可以判断链表是否有环了,那么接下来要找这个环的入口了

假设从头结点到环形入口节点 的节点数为x。
环形入口节点到 fast指针与slow指针相遇节点 节点数为y。
从相遇节点 再到环形入口节点节点数为 z。
在这里插入图片描述

那么相遇时:(x + y) * 2 = x + y + n (y + z)

两边消掉一个(x+y): x + y = n (y + z)

整理公式之后为如下公式:x = (n - 1) (y + z) + z

当 n为1的时候,公式就化解为 x = z

这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

//快慢指针
public ListNode EntryNodeOfLoop(ListNode pHead) {
        if(pHead == null) return null;
        // 定义快慢指针
        ListNode slow = pHead;
        ListNode fast = pHead;
        while(fast != null && fast.next != null){
            // 快指针是满指针的两倍速度
            fast = fast.next.next;
            slow = slow.next;
            // 记录快慢指针第一次相遇的结点
            if(slow == fast) break;
        }
        // 若是快指针指向null,则不存在环
        if(fast == null || fast.next == null) return null;
        // 重新指向链表头部
        fast = pHead;
        // 与第一次相遇的结点相同速度出发,相遇结点为入口结点
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }

JZ22 链表中倒数最后k个结点 【难!!!】

要求:空间复杂度 O(n),时间复杂度 O(n)

进阶:空间复杂度 O(1),时间复杂度 O(n)

import java.util.*;
// 方法:快慢指针
public class Solution {
    public ListNode FindKthToTail (ListNode pHead, int k) {
        ListNode left=pHead,right=pHead;
        while(right!=null && k>0){
            right=right.next;
            k--;
        }
        if(k>0) return null;
        while(right!=null){
            right=right.next;
            left=left.next;
        }
        return left;
    }
}

JZ35 复杂链表的复制 【难!!!】

思路:

使用map保存原链表节点,遍历map,构建next和random指针,返回头指针

import java.util.*;
public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        RandomListNode cur = pHead;
        HashMap<RandomListNode, RandomListNode> map = new HashMap<>();
        //建立 “原节点 -> 新节点” 的 Map 映射
        while (cur != null) {
            map.put(cur, new RandomListNode(cur.label));
            cur = cur.next;
        }
        //构建新链表的 next 和 random 指向
        cur = pHead;
        while (cur != null) {
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        //返回新链表的头节点
        return map.get(pHead);
    }
}

JZ76 删除链表中重复的结点

【这个题有bug!!】

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        if (pHead == null || pHead.next == null) return pHead;
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = pHead;
        //注意遍历范围控制??
        //一定要注意操作时不能直接拿头结点去操作??
        ListNode p = dummyHead;
        int x = 0;
        while (p.next != null && p.next.next != null ) {
            if ( p.next.val == p.next.next.val) {
                x = p.next.val;
//                 p.next = p.next.next;
                //如果不写p.next != null那么编译器将会自动检查p.next.next==null?,如果在if中加入p.next != null,那么先判断是否为true
                while (p.next != null && p.next.val == x) {
                    p.next = p.next.next;
                }
            }
            //这种情况无法处理 [1,1]这种!! 因为p.next.next != null
//             else if (p.next.val == x) {
//                 p.next = p.next.next;
//             }

            else {
                p = p.next;
            }
        }
        return dummyHead.next;
    }
}

二、树

JZ77 按之字形顺序打印二叉树

从哪端进,从哪端出

第一层:从左进左出,第二层:右进右出

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        Deque<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> res = new ArrayList<>();
        if(root==null) return res;
        queue.offerLast(root);
        int level = 1;
        while (!queue.isEmpty()) {
            List<Integer> path = new ArrayList<>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                if (level % 2 != 0) {
                    TreeNode node = queue.pollFirst();
                    path.add(node.val);
                    if (node.left != null)
                        queue.offerLast(node.left);
                    if (node.right != null)
                        queue.offerLast(node.right);
                } else {
                    TreeNode node = queue.pollLast();
                    path.add(node.val);
                    if (node.right != null)
                        queue.offerFirst(node.right);
                    if (node.left != null)
                        queue.offerFirst(node.left);
                }
            }
            level++;
            res.add(path);
        }
        return res;
    }
}

JZ54 二叉搜索树的第k个节点

进阶:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

时间复杂度 O(N)O(N) : 当树退化为链表时(全部为右子节点),无论 kk 的值大小,递归深度都为 NN ,占用 O(N)O(N) 时间。
空间复杂度 O(N)O(N) : 当树退化为链表时(全部为右子节点),系统使用 O(N)O(N) 大小的栈空间。

import java.util.*;
public class Solution {

    public int KthNode (TreeNode proot, int k) {
        ArrayList<Integer> list = new ArrayList<>();
        if (proot == null || k == 0 )
            return -1;
        preOrder(proot, list);
        if (k > list.size())
            return -1;
        Collections.sort(list);
        return list.get(k - 1);
    }

    public void preOrder(TreeNode root, ArrayList list) {
        if (root != null) {
            list.add(root.val);
            preOrder(root.left, list);
            preOrder(root.right, list);
        }
    }
}

JZ26 树的子结构

  1. 判断根节点是否重合
    1. 如果重合,判断左右子树是否对应
  2. 否则左子树中是否包含子结构,
  3. 右子树是否包含子结构
public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
         //特例处理
        if (root1 == null || root2 == null) return false;
//         分为三种情况:1 根节点比较,2.和左子树比较,3.和右子树比较
        return isSame(root1, root2) || HasSubtree(root1.left, root2) ||
               HasSubtree(root1.right, root2);
    }

      public boolean isSame(TreeNode root1, TreeNode root2) {
        //终止条件 root2为空,表示越过叶节点,B树匹配完成
        if (root2 == null) return true;
        //root1为空,表示越过叶节点,匹配失败
        if (root1 == null || root1.val != root2.val) return false;

        return isSame(root1.left, root2.left) && isSame(root1.right, root2.right);
    }
}

JZ33 二叉搜索树的后序遍历序列

要求:空间复杂度 O(n),时间时间复杂度 O(n^2)

方法一:递归分治

递归解析:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if (sequence.length == 0) return false;
        return recur(sequence, 0, sequence.length - 1);
    }

    public boolean recur(int [] sequence, int l, int r) {
        //终止条件 当前树只有一个节点
        if (l >= r) return true;
        //递推工作
        int pos = l;
        while (sequence[p] < sequence[r])pos++;
        //第一个右子树节点
        int mid = pos;
        //继续向后遍历 判断右子树
        while (sequence[pos] > sequence[r]) pos++;

        return pos == r && recur(sequence, l, mid - 1)  && recur(sequence, mid, r - 1);
    }
}

JZ34 二叉树中和为某一值的路径(二)

import java.util.ArrayList;
public class Solution {
    ArrayList<ArrayList<Integer>> res = new ArrayList<>();
    ArrayList<Integer> path = new ArrayList<>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int expectNumber) {
        //终止条件
        if (root == null) return res;

        //递推工作
        expectNumber -= root.val;
        path.add(root.val);
        if (expectNumber == 0 && root.left == null && root.right == null) {
            res.add(new ArrayList(path));
        }
        FindPath(root.left, expectNumber);
        FindPath(root.right, expectNumber);
        //注意此处??
        path.remove(path.size() - 1);
        return res;
    }
}

JZ8 二叉树的下一个结点 【记公式!!!】

要求:空间复杂度 O(1) ,时间复杂度 O(n)

结合图,我们可发现分成两大类:

1、当前节点有右子树的,那么下个结点就是右子树最左边的点;

2、当前节点没有右子树的,也可以分成两类:

​ a)当前节点是父节点左孩子,那么父节点就是下一个节点 ;

​ b)当前节点是父节点的右孩子找他的父节点的父节点的父节点…直到当前结点是其父节点的左孩子位置。如果没有,那么他就是尾节点。

public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        //1、有右子树的,那么下个结点就是右子树最左边的点;
        if (pNode.right != null) {
            TreeLinkNode node = pNode.right;
            while (node.left != null)
                node = node.left;
            return node;
        } else {
            while (pNode.next != null) {
                TreeLinkNode parent = pNode.next;
                //a)是父节点左孩子,那么父节点就是下一个节点 ;
                if (parent.left == pNode)
                    return parent;
                //b)是父节点的右孩子找他的父节点的父节点的父节点...直到当前结点是其父节点的左孩子位置尾节点。
                pNode = parent;
            }
        }
        return null;
    }
}

JZ84 二叉树中和为某一值的路径(三)

import java.util.*;
public class Solution {
    int res = 0;
    public int FindPath (TreeNode root, int sum) {
        if(root==null) return res;
        recur(root, sum);
        FindPath(root.left, sum);
        FindPath(root.right, sum);
        return res;
    }
    public void recur(TreeNode root, int sum) {
        //终止条件
        if (root == null) return ;

        //递推工作
        sum -= root.val;
        if (sum == 0)
            res += 1;
        recur(root.left, sum);
        recur(root.right, sum);
    }
}

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

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

时间复杂度 O(N) : N为二叉树的节点数,中序遍历需要访问所有节点。
空间复杂度 O(N) : 最差情况下,即树退化为链表时,递归深度达到 N,系统使用 O(N)栈空间。

本文解法基于性质:二叉搜索树的中序遍历为 递增序列
将 二叉搜索树 转换成一个 “排序的循环双向链表” ,其中包含三个要素:

排序链表: 节点应从小到大排序,因此应使用 中序遍历 “从小到大”访问树的节点。
双向链表: 在构建相邻节点的引用关系时,设前驱节点 pre 和当前节点 cur ,不仅应构建 pre.right = cur ,也应构建 cur.left = pre 。
循环链表: 设链表头节点 head 和尾节点 tail ,则应构建 head.left = tail 和 tail.right = head 。!!

class Solution {
    Node head, pre;
    public Node treeToDoublyList(Node root) {
        if(root==null) return null;
        dfs(root);
        
		//head 指向头节点, pre 指向尾节点,位置可以交换
        pre.right = head;
        head.left =pre;
        return head;
    }

    public void dfs(Node cur){
        if(cur==null) return;
        
        dfs(cur.left);
        //pre用于记录双向链表中位于cur左侧的节点,即上一次迭代中的cur,当pre==null时,cur左侧没有节点,即此时cur为双向链表中的头节点!!
        if(pre==null) head = cur;
        //pre!=null时,cur左侧存在节点pre,需要进行pre.right=cur的操作。
        else 
            pre.right = cur;
       //pre是否为null对这句没有影响,且这句放在上面两句if else之前也是可以的。
        cur.left = pre;
        pre = cur;//pre指向当前的cur
        dfs(cur.right);//全部迭代完成后,pre指向双向链表中的尾节点
    }
}

剑指 Offer 54. 二叉搜索树的第k大节点

剑指 Offer 54. 二叉搜索树的第k大节点

class Solution {
    int count = 0;
    int res = 0;
    public int kthLargest(TreeNode root, int k) {
        recur(root, k);
        return res;
    }
    private void recur(TreeNode root, int k) {
        if (root == null) return;

        recur(root.right, k);
        if (++count == k) res = root.val;
        recur(root.left, k);
    }
}

整理不易,关注和收藏后拿走!
欢迎专注我的公众号:AdaCoding 和 Github:AdaCoding123
在这里插入图片描述

  • 12
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值