剑指offer思路总结
1、链表
1.1 从尾到头打印链表
题目概述:给出一个链表的表头,从尾到头反过来打印出每个结点的值。
(1)若可以改变链表
思路:把链表反转,在遍历。
(2)不能改变链表
思路:可以借助辅助栈的后进先出;
可以利用递归实现。
1.2 删除链表的结点
题目一大致描述:给定一个单向链表的头结点和一个结点指针,要求在O(1)时间内删除该节点。
思路:因为要求是O(1),所以考虑先复制后结点的值,然后删除。需要注意头结点,尾结点情况。
题目二大致描述:链表有序,删除链表中重复的结点
思路:为了避免头结点可能被删除的情况,引进一个新的头结点,然后使用两个指针pre,cur进行即可。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head == null)
return null;
ListNode newHead = new ListNode(-1);
newHead.next = head;
ListNode cur = head; //当前节点
ListNode pre = newHead; //前驱节点
while(cur != null && cur.next != null){
if(cur.val == cur.next.val){ //判断是否相同
int a = cur.val;
while(cur != null && cur.val == a){ //找出不相同的节点
cur = cur.next;
}
pre.next = cur;
// pre = cur; 如果多了这条,会出错,所以需要注意
}else{
pre = cur;
cur = cur.next;
}
}
return newHead.next;
}
}
题目三:去重复元素,但每一个只保留一次。
思路:一次遍历,出现相同就跳一个指针。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode current = head;
while(current != null && current.next != null){
if(current.val == current.next.val){
current.next = current.next.next;
}else {
current = current.next;
}
}
return head;
}
}
1.3 链表中的倒数第k个结点
题目大致概述:给出一个链表,输出链表中倒数第k个结点。
思路:双指针,一个先走k步,然后同时起步。
1.4 链表中环的入口
题目概述:一个链表中包含环,找出环的入口。
思路: 双指针,一个每次走一步,一个每次走两步,当相遇时,第二个指针刚好比第一个走多了一个环大小的距离,然后一个指针从头节点重新开始走,相遇时的节点就是环的入口。
1.5 反转链表
题目概述:给定一个链表,反转该链表。
思路:双指法。一个指针记录为反转的链表头节点,一个是反转了的链表的头节点。
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null)
return head;
ListNode pre = null;
ListNode cur = head;
while(cur != null){
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
1.6 合并两个排序的链表
题目概述:给定俩个递增的链表,合并两个链表,使其依旧是递增排序。
思路:递归法。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null)
return l2;
if(l2 == null)
return l1;
ListNode head = null;
if(l1.val > l2.val){
head = l2;
head.next = mergeTwoLists(l1 , l2.next);
}else {
head = l1;
head.next = mergeTwoLists(l1.next , l2);
}
return head;
}
}
1.7 复杂链表的复制
题目描述:给定一个复杂的链表,包括一个next指针和一个指向随意地方的指针。复制一份。
思路:两次遍历法,第一次遍历是在原链表的每一个结点后复制一份新的结点;第二次是对随机指针的复制并分开链表。
class Solution {
public Node copyRandomList(Node head) {
if(head == null)
return head;
changeFirst(head);
changeSecond(head);
return changeThird(head);
}
//进行next结点的复制
private void changeFirst(Node head){
Node root = head;
while(root != null){
Node node = new Node(0);
node.val = root.val;
node.next = root.next;
root.next = node;
root = node.next;
}
}
//ramdom结点的复制
private void changeSecond(Node head){
Node root = head;
while(root != null){
Node node = root.next;
if(root.random != null)
node.random = root.random.next;
root = node.next;
}
}
//进行拆分
private Node changeThird(Node head){
Node phead = head;
Node nCloneHead = null;
Node nCloneNode = null;
if(phead != null){
nCloneHead = nCloneNode = phead.next;
phead.next = nCloneHead.next;
phead = phead.next;
}
while (phead != null){
nCloneNode.next = phead.next;
nCloneNode = nCloneNode.next;
phead.next = nCloneNode.next;
phead = phead.next;
}
return nCloneHead;
}
}
1.8 二叉搜索树与双向链表
题目描述:给定一棵二叉搜索树,将二叉搜索树转成一棵排序的双向链表。不能创建新节点,只能调整树中结点指针的指向。
思路:利用递归法。
/**
*算法流程:
*dfs(cur): 递归法中序遍历;
*
*终止条件: 当节点 curcur 为空,代表越过叶节点,直接返回;
*递归左子树,即 dfs(cur.left) ;
*构建链表:
*当 prepre 为空时: 代表正在访问链表头节点,记为 headhead 。
*当 prepre 不为空时: 修改双向节点引用,即 pre.right = curpre.right=cur , cur.left = *precur.left=pre ;
*保存 curcur : 更新 pre = curpre=cur ,即节点 curcur 是后继节点的 prepre ;
*递归右子树,即 dfs(cur.left) ;
**/
class Solution {
Node pre, head;
public Node treeToDoublyList(Node root) {
if(root == null) return null;
dfs(root);
head.left = pre;
pre.right = head;
return head;
}
void dfs(Node cur) {
if(cur == null) return;
dfs(cur.left); //遍历左孩子
//如果为第一个最左孩子,设置为头结点,否则更新
if(pre != null) pre.right = cur;
else head = cur;
cur.left = pre;
pre = cur;
//递归右孩子
dfs(cur.right);
}
}
1.9 序列化二叉树
题目概述:实现两个函数,分别用来序列化和反序列化二叉树。
思路:序列化时利用前序遍历,当遇到null ,用&表示;反序列化时,利用一个全局变量,构造即可。
public class Codec {
private String results; // 定义一个成员变量存储序列化结果
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if (root == null) {
return "[]";
} // root为null直接返回空字符串即可
// 创建一个队列存储每一个非null节点
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root); // 将root先放进去
results = "[" + root.val;
while(!queue.isEmpty()) {
// 每次从队列中取出一个节点,根据其左右子节点是否为null进行字符串拼接
TreeNode tmp = queue.poll();
if (tmp.left != null) {
queue.offer(tmp.left);
results += "," + tmp.left.val;
} else {
results += ",null";
}
// 上面处理left,下面处理right
if (tmp.right != null) {
queue.offer(tmp.right);
results += "," + tmp.right.val;
} else {
results += ",null";
}
}
// 处理完成之后添加上结束符
results += "]";
return results;
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if (data.length() == 2) {
return null;
}
// 去除收尾的中括号字符
data = data.substring(1, data.length() - 1);
// 利用逗号分隔符获取每一个节点的值
String[] vals = data.split(",");
// 定义队列存储每一个有效节点,为了构建其左右子节点
Queue<TreeNode> queue = new LinkedList<TreeNode>();
TreeNode root = new TreeNode(Integer.valueOf(vals[0]));
queue.offer(root); // 第一个元素为root节点
int i = 1; // 标记后续处理的节点值(包含null)
while (!queue.isEmpty()) {
TreeNode tmp = queue.poll();
// 从队列中取出节点,然后根据是否为null,依次设置left和right
// 如果不是null,则需要加入队列,后续需要处理此有效节点的左右节点
if (vals[i].equals("null")) {
tmp.left = null;
} else {
tmp.left = new TreeNode(Integer.valueOf(vals[i]));
queue.offer(tmp.left);
}
i++;
if (vals[i].equals("null")) {
tmp.right = null;
} else {
tmp.right = new TreeNode(Integer.valueOf(vals[i]));
queue.offer(tmp.right);
}
i++;
}
return root;
}
}
1.10 两个链表的第一个公共节点
题目概述:输入两个链表,找出它们的第一个公共节点。
思路: 采用路径法。也就是让两个指针同时走一遍两个链表,相遇时的点就是第一个公共节点。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode list1 = headA;
ListNode list2 = headB;
while(list1 != list2){
list1 = list1 == null ? headB : list1.next;
list2 = list2 == null ? headA : list2.next;
}
return list1;
}
}
2、树
2.1 重建二叉树
题目概述:输入某个二叉树的前序遍历和中序遍历的结果,重建该二叉树。
思路:在二叉树的前序遍历序列中,第一个数字总是树的根结点的值。但在中序遍历序列中,根结点的值在序列的中间,左子树的结 点的值位于根结点的值的左边,而右子树的结点的值位于根结点的值的右边。因此我们需要扫描中序遍历序列,才能找到根结点的 值。
分别找到了左、右子树的前序遍历序列和中序遍历序列,我们就可以用同样的方法分别去构建左右子树。换句话说,这是一个递归的过程。
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length == 0)
return null;
return reConstructBinaryTree(preorder , 0 , preorder.length - 1 ,
inorder , 0 , inorder.length - 1);
}
private TreeNode reConstructBinaryTree(int [] pre,int startPre,int endPre,int [] in,int startIn,int endIn) {
if(startPre>endPre||startIn>endIn)
return null;
TreeNode root=new TreeNode(pre[startPre]);
for(int i=startIn;i<=endIn;i++)
if(in[i]==pre[startPre]){
root.left=reConstructBinaryTree(pre , startPre+1, startPre+i-startIn, in, startIn, i - 1);
root.right=reConstructBinaryTree(pre, startPre + i - startIn + 1,endPre, in, i + 1, endIn);
}
return root;
}
}
2.2 二叉树的下一个节点
题目概述:给定一棵二叉树和其中的一个节点,其中二叉树还包含一个指向父节点的指针,如何找出中序遍历的下一个节点。
思路:分三种情况进行分析:
(1)如果给出的结点有右子树,那么它的下一个节点就是它的右子树的最左子结点;
(2)如果给出的结点没有右子树,如果该节点是它父节点的左子树,那么下一个节点就是父节点;
(3)如果给出的结点既没有右子树,也不是父节点的左子树,而是父节点的右子树,那么可以沿着指向父节点的指针一直向上遍历,直到找到一个是它父节点的左子结点,若没找到,则下一个节点为null。
2.3 树的子结构
题目概述:输入两颗二叉树A和B,判断B是不是A的子结构。
思路:递归法。先判断第一个节点是否相同,若相同,判断以该节点开始的树是否含有子结构;若不是,递归左右子树。
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A == null || B == null)
return false;
boolean result = false;
if(A != null && B != null){
if(A.val == B.val){
result = isTree(A , B);
}
if(!result){
result = isSubStructure(A.left , B);
}
if(!result){
result = isSubStructure(A.right , B);
}
}
return result;
}
private boolean isTree(TreeNode A , TreeNode B){
if(B == null)
return true;
if(A == null)
return false;
if(A.val == B.val){
return isTree(A.left , B.left) && isTree(A.right , B.right);
}
return false;
}
}
2.4二叉树的镜像。
题目概述:给定一颗二叉树,输出其镜像二叉树。
思路:利用递归法即可。
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root == null)
return null;
TreeNode head = root;
TreeNode temp = head.left;
head.left = mirrorTree(root.right);
head.right = mirrorTree(temp);
return head;
}
}
2.5 对称二叉树。
题目概述:给定一颗二叉树,判断这颗二叉树是否为对称二叉树。如果一颗树与他的镜像二叉树相同,则认为他是是一颗对称二叉树。
思路:利用递归遍历法。如果一颗二叉树为对称二叉树,那么他的前序遍历和后续遍历应该是相同的。
class Solution {
public boolean isSymmetric(TreeNode root) {
return isHashTree(root , root);
}
private boolean isHashTree(TreeNode root1 , TreeNode root2){
if(root1 == null && root2 == null)
return true;
if(root1 == null || root2 == null)
return false;
if(root1.val != root2.val)
return false;
return isHashTree(root1.left , root2.right)
&& isHashTree(root1.right , root2.left);
}
}
2.6 从上到下打印二叉树
题目一概述:给定一颗二叉树,同一层的节点从左到右打印(不分行)。
思路:借助一个队列,每次遍历完队列的一个节点时,就将该节点不为空的节点进队列。
class Solution {
public int[] levelOrder(TreeNode root) {
if(root == null)
return new int[]{};
ArrayList<Integer> list = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
list.add(node.val);
if(node.left != null)
queue.offer(node.left);
if(node.right != null)
queue.offer(node.right);
}
int[] result = new int[list.size()];
for(int i = 0; i < result.length; ++i){
result[i] = list.get(i);
}
return result;
}
}
题目二概述:给定一颗二叉树,同一层的节点从左到右打印(分行)。
思路:和题目一一样,不过需要定义两个变量参数,一个记录当前行的节点数,一个记录下一行的节点数。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
if(root != null) queue.add(root);
while(!queue.isEmpty()) {
List<Integer> tmp = new ArrayList<>();
for(int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
tmp.add(node.val);
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(tmp);
}
return res;
}
}
题目三概述:给定一颗二叉树,之字形打印(分行)。
思路:借助一个队列即可实现。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
if(root != null) queue.add(root);
while(!queue.isEmpty()) {
LinkedList<Integer> tmp = new LinkedList<>();
for(int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
if(res.size() % 2 == 0) tmp.addLast(node.val); // 偶数层 -> 队列头部
else tmp.addFirst(node.val); // 奇数层 -> 队列尾部
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(tmp);
}
return res;
}
}
2.7 二叉搜索树的后序遍历序列
题目概述:给定一个数组,判断该数组是否为二叉搜索树的后序遍历结果。
思路:递归,后序遍历的最后一个元素为根节点。可以根据根节点找出其左子树,右子树,然后分别对其子树进行递归判断即可。
class Solution {
public boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length - 1);
}
boolean recur(int[] postorder, int i, int j) {
if(i >= j) return true;
int p = i;
while(postorder[p] < postorder[j]) p++;
int m = p;
while(postorder[p] > postorder[j]) p++;
return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j - 1);
}
}
2.8 二叉树中和为某一值得路径
题目概述:给定一个二叉树和一个整数,打印出二叉树中和为节点值得所有路径。(路径就是从根节点到叶子节点形成的路径 )
思路:可以采用递归方法,利用树的前序遍历。
class Solution {
List<Integer> list = new ArrayList<>();
List<List<Integer>> lists = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
if(root == null){
return lists;
}
pathS(root,sum);
return lists;
}
public void pathS(TreeNode root, int sum){
if(root == null){
return;
}
list.add(root.val);
sum -= root.val;
if(root.left == null && root.right == null && sum == 0){ //符合条件,记录下来
lists.add(new ArrayList(list));
list.remove(list.size()-1); //移除list的最后一个元素,回朔上一步
return;
}
pathS(root.left,sum);
pathS(root.right,sum);
list.remove(list.size()-1);
}
}
2.9 二叉搜索树的第k大节点
题目概述:给定一个二叉搜索树,找出其中第k大的节点。
思路:(1) 可以利用二叉树的中序遍历(不过是先从右子节点开始遍历);
(2)也可以借助栈进行深度遍历,到k为止。
class Solution {
int res, k;
public int kthLargest(TreeNode root, int k) {
this.k = k;
dfs(root);
return res;
}
void dfs(TreeNode root) {
if(root == null) return;
dfs(root.right);
if(k == 0) return;
if(--k == 0) res = root.val;
dfs(root.left);
}
}
2.10 二叉树的深度
题目一概述:二叉树的深度。
思路:直接采用递归法就可以搞定。
class Solution {
public int maxDepth(TreeNode root) {
if(root == null)
return 0;
int leftDe = maxDepth(root.left); //递归计算左子树高度
int rightDe = maxDepth(root.right); //递归计算右子树高度
return leftDe > rightDe ? leftDe + 1 : rightDe + 1;
}
}
题目二概述:判断一棵是否为平衡二叉树
思路:递归,但是注意重复节点的遍历。
class Solution {
public boolean isBalanced(TreeNode root) {
if(root == null)
return true;
return depth(root) != -1;
}
private int depth(TreeNode root){
if(root == null)
return 0;
int leftDepth = depth(root.left); //递归左子树
if(leftDepth == -1){
return -1;
}
int rightDepth = depth(root.right); //递归右子树
if(rightDepth == -1){
return -1;
}
//若子树不满足平衡条件,直接返回 -1 ,否则返回该子树的深度
return Math.abs(leftDepth - rightDepth) > 1 ? -1 : Math.max(leftDepth , rightDepth) + 1;
}
}