剑指offer分类

一、栈和队列

1.用两个栈实现队列

问题描述:

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

import java.util.Stack;

public class StackToQueue {
	Stack<Integer> s1 = new Stack<>();
	Stack<Integer> s2 = new Stack<>();
	public static void main(String[] args) {
		StackToQueue st = new StackToQueue();
		st.push(1);
		st.push(2);
		st.push(3);
		System.out.println(st.pop());
		System.out.println(st.pop());
		System.out.println(st.pop());
	}
	
	public void push(int node) {
		s1.push(node);
	}
	
	public int pop() {
		if(!s1.isEmpty() && !s2.isEmpty()) {
			throw new RuntimeException("stack is not empty");
		}
		if(s2.isEmpty()) {
			while(!s1.isEmpty()) {
				s2.push(s1.pop());
			}
		}
		//将s2全部出栈后才能再入栈
		return s2.pop();
	}

}

相关知识点:

1.栈的入栈和出栈

2.入队:将元素进栈A

   出队:判断栈B是否为空,如果为空,则将栈A中所有元素pop,并push进栈B,栈B出栈; 如果不为空,栈B直接出栈。


2.两个队列形成栈

问题描述:

用两个队列来实现一个栈,完成push和pop操作。

import java.util.LinkedList;

public class QueueToStack {
	LinkedList<Integer> l1 = new LinkedList<>();
	LinkedList<Integer> l2 = new LinkedList<>();
	public static void main(String[] args) {
		QueueToStack q1 = new QueueToStack();
		q1.push(1);
		q1.push(2);
		q1.push(3);
		q1.push(4);
		System.out.println(q1.pop());
		System.out.println(q1.pop());
		System.out.println(q1.pop());
		System.out.println(q1.pop());
	}
	public void push(int node) {
		l1.add(node);
	}
	
	public int pop() {
		if(!l1.isEmpty() && !l2.isEmpty()) {
			throw new RuntimeException("Queue is not empty");
		}
		
		if(l2.isEmpty()) {
			while(l1.size() > 1) {
				l2.add(l1.poll());
			}
		}
		swap();
		return l2.poll();
	}
	
	public void swap() {
		LinkedList<Integer> tmp = l2;
		l2 = l1;
		l1 = tmp;
	}

}

相关知识点:

1.将一个队列l1作为入栈,l2作为出栈,将l1除最后一个元素均入到l2中,交换l1和l2,目前l2中存储的是需要出栈得元素。


3.栈的压入、弹出序列

题目描述:

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

import java.util.*;

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        Stack<Integer> st = new Stack<>();
        if(pushA == null || popA == null || pushA.length != popA.length){
            return false;
        }
        int i = 0, j = 0;
        while(j < popA.length){
            while(st.isEmpty() || st.peek() != popA[j]){
                if(i == pushA.length){
                    return false;
                }
                st.push(pushA[i++]);
            }
            st.pop();
            j++;
        }
        return true;
    }
}

相关知识点:

1.利用一个新栈,将原序列元素压入栈,当某个元素与第二个序列的第一个元素相等时,停止入栈,将该元素出栈。再判断栈顶元素是否与第二个序列的第二个元素相等,相等则继续出栈,不等则继续入栈。当元素都入栈后,仍未找到相等元素,则匹配失败,否则成功。


4.包含min函数的栈

题目描述:

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。

import java.util.Stack;
import java.util.*;

public class Solution {

    Stack<Integer> stack = new Stack<Integer>();
    public void push(int node) {
        stack.push(node);
    }
    
    public void pop() {
        stack.pop();
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int min() {
        int min = stack.peek();
        int tem = 0;
        Iterator<Integer> iterator = stack.iterator();
        while(iterator.hasNext()){
            tem = iterator.next();
            if(min > tem){
                min = tem;
            }
        }
        return min;
    }
}

相关知识点:

1.iterator迭代器:next()获得序列中的下一个元素

                             hasnext()检查序列中是否还有元素

                             remove()将迭代器新返回的元素删除


 

 


二、链表

1.从尾到头打印链表

问题描述:

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

import java.util.Stack;

public class LinkedList {
	public static void main(String[] args) {
		int[] a = {1,2,3,4,5,6};
		push(a);
	}
	
	public static void push(int[] a) {
		Stack<Integer> st = new Stack<>();
		for(int i = 0; i < a.length; i++) {
			st.push(a[i]);
		}
		while(!st.isEmpty()) {
			System.out.print(st.pop());
		}
	}
}

相关知识点:

1.利用栈,存入栈后再出栈


2.链表中倒数第k个结点

题目描述:

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

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head == null || k <= 0){
            return null;
        }
        ListNode p1=head, p2=head;
        //前一个指针先走k-1步
        for(int i = 0; i < k; i++){
            if(p1 != null){
                p1 = p1.next;
            }else{
                return null;
            }
        }
        //两个指针从第k步开始同时走
        while(p1 != null){
            p1 = p1.next;
            p2 = p2.next;
        }
        return p2;
    }
}

相关知识点:

1.取两个指针,第一个指针先走k-1步,第二个指针从第一个指针的第k步开始走,当第一个指针遍历完链表后,第二个指针刚好在倒数第k个位置。


3.反转链表

题目描述:

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

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        ListNode pre = null;
        ListNode tmp = null;
        if(head == null || head.next == null){
            return head;
        }
        while(head != null){
            tmp = head.next;
            head.next = pre;
            pre = head;
            head = tmp;
        }
        return pre;
    }
}

相关知识点:

1.取两个指针,pre指向当前结点head的前一个结点,tmp指向当前结点head的后一个结点。将head.next先保存在tmp中,防止链表因断裂而丢失后一个结点。将pre给head.next相当于将head的指针指向pre,实现head到pre的反转,然后将pre指针向后移一位指向原来的head,将head指针也向后移一位,指向原来的tmp。

 


4.合并两个排序的链表

题目描述:

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

/*
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;
        }
        if(list1.val <= list2.val){
            list1.next = Merge(list1.next,list2);
            return list1;
        }else{
            list2.next = Merge(list1,list2.next);
            return list2;
        }
    }
}

相关知识点:

1.对比两个链表的头节点,得到较小的头节点作为新链表的头节点,再对比被拿走头节点的链表的下一个结点和另一个完整结点的头节点,找到较小的头节点连接到已合并的链表后。


5.链表中环的入口结点

题目描述:

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

使用哈希表的方法:

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

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

    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead == null || pHead.next == null || pHead.next.next == null){
            return null;
        }
        HashSet<ListNode> hashSet = new HashSet<ListNode>();
        ListNode enter = null;
        while(pHead != null){
            boolean flag = hashSet.add(pHead);
            if(flag == false){
                enter = pHead;
            }
            pHead = pHead.next;
        }
        return enter;
    }
}

快慢指针方法

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

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

    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead == null || pHead.next == null || pHead.next.next == null){
            return null;
        }
        ListNode p1 = pHead;
        ListNode p2 = pHead;
        while(p1.next != null && p2.next != null){
            p1 = p1.next.next;
            p2 = p2.next;
            if(p1 == p2){
                p1 = pHead;
                while(p1 != p2){
                    p1 = p1.next;
                    p2 = p2.next;
                }
                return p1;
            }
        }
        return null;
    }
}

相关知识点:

1.遍历链表,将节点存储到set中(注意存储的是节点不是值),通过add()方法的返回值可判断是否添加成功从而判断是否重复,如果重复则说明有环,并且入环节点为第一个重复节点。

2.快慢指针

快指针一次2步,慢指针一次1步。快慢指针同时出发,若链表有环,快慢指针一定会相遇。相遇之后,快指针回到链表头,变为一次走1步与慢指针一起开始移动,则快慢指针一定在入环处相遇。

 

  • 设x为环前面的路程(黑色路程),y为环入口到相遇点的路程(绿色路程), z为环的长度(绿色+黄色路程)
  • 当快慢指针相遇的时候:

慢指针走的路程为S慢 = x + m * z + y

快指针走的路程为S快 = x + n * z + y

因为快指针速度是慢指针的两倍,所以2*S慢 = S快

从而可以推导出:

x = (n - 2 m )*z - y= (n - 2 m -1 )*z + z - y

即环前面的路程 = 数个环的长度(可能为0)+z-y(z-y即为黄色路程)

所以,相遇之后让一个指针从起点A开始走,另一个指针从相遇点B开始继续往后走,两个指针速度一样,那么,当从原点的指针走到环入口点的时候从相遇点开始走的那个指针也一定刚好到达环入口点。


6.两个链表的第一个公共结点

题目描述:输入两个链表,找出它们的第一个公共结点

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        if(pHead1 == null || pHead2 == null){
            return null;
        }
        int length1 = getLength(pHead1);
        int length2 = getLength(pHead2);
        int k = length1 - length2;
        if(k > 0){
            for(int i =0; i < k; i++){
                pHead1 = pHead1.next;
            }
        }else if(k < 0){
            k *= -1;
            for(int i =0; i < k; i++){
                pHead2 = pHead2.next;
            }
        }
            while(pHead1 != null){
                if(pHead1.val == pHead2.val){
                    return pHead1;
                }
                pHead1 = pHead1.next;
                pHead2 = pHead2.next;
            }
        return null;
    }
    
    //获取链表长度
    public int getLength(ListNode list){
        int count =0;
        while(list != null){
            list = list.next;
            count++;
        }
        return count;
    }
}

相关知识点:

1.先计算出两个链表的长度差k,让长的链表先走k步,然后两个链表依次向后遍历并且比较元素值。


7.删除链表中值为value的结点


void deleteSpecialData(Node* &L,int x) {	

	Node* r;

	r = L;

	while(r->next!=NULL){//当没有到达链表尾部的时候,继续循环 

		if(r->next->data == x){

			Node* q;//申请一个指针q 

			q=r->next;//保存L->next的指针位置 

			r->next = r->next->next;//改变链表的指针 

			delete(q);//释放节点空间q 

		} 

		else

		r= r->next;//往下循环 

	}

}

相关知识点:

1.我们删除节点A,必须找到结点A的前一个结点,然后修改起后继结点,所以循环里用的是r->next->data,这里的r指的得是当前的判断结点。同样,所以while()循环中也是r->next!=NULL


8.利用冒泡排序排列链表

//冒泡排序
    public static ListNode bubbleSort(ListNode head){
        if(head == null || head.next == null)  //链表为空或者仅有单个结点
            return head;
        
        ListNode cur = null, tail = null;
        
        cur = head;
        
        while(cur.next != tail){
            while(cur.next != tail){
                if(cur.val > cur.next.val){
                    int tmp = cur.val;
                    cur.val = cur.next.val;
                    cur.next.val = tmp;
                }
                cur = cur.next;
            }
            
            tail = cur;  //下一次遍历的尾结点是当前结点
            cur = head;     //遍历起始结点重置为头结点    
        }
        
        return head;
        
        
    }

相关知识点:

1.从链表的投结点开始,将大的数依次往后移。tail=cur相当于将tail移到当前cur的位置,也就是将cur的值赋给tail,cur继续回到头节点开始遍历,尾节点是该链表中最大的数。


9.链表去重


class ListNode {

    int val;

    ListNode next = null;

 

    ListNode(int val) {

        this.val = val;

    }

}

 

public class Solution {

    public ListNode deleteDuplication(ListNode pHead)

    {

       ListNode first = new ListNode(-1);

       first.next = pHead;

       ListNode last = first;

       ListNode p = pHead;

 
       while(p!=null&&p.next!=null){

           if(p.val==p.next.val){

               int val = p.val;

               while(p!=null&&p.val==val){

                   p = p.next;

               last.next = p;

               }

           }else{

               last = p;

               p = p.next;

           }

       }
       return first.next;

    }

}

相关知识点:

1.可以定义一个first对象(值为-1,主要用于返回操作后的链表),first.next指向head,定义一个last同样指向first(主要用于操作记录要删除节点的前一个节点),定义一个p指向head,指向当前节点。
2.操作流程:
①先判断当前p指向与p.next指向是否为空,为空则进入⑤,不为空进入②;
②判断当前节点p的值与与p.next的值是否相等,如果相等,进入③,不相等,进入④
③记录p.val的值为val,循环判断val是否与当前p指向相等,相等的话p = p.next,last.next指向p;
④last指向p,p指向p.next;
⑤返回first.next;


三、二叉树

1.重建二叉树

题目描述:

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。


2.从上往下打印二叉树

题目描述:

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

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

    }

}
*/
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        if(root == null){
            return arrayList;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.add(root);
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            arrayList.add(node.val);
            if(node.left != null){
                queue.add(node.left);
            }
            if(node.right != null){
                queue.add(node.right);
            }
        }
        return arrayList;
    }
}

 相关知识点:

1.树的按层遍历

2.需要一个容器和一个数组,当从根节点开始遍历时,需要将根节点的左右子树存进容器中,将根节点的值存入数组;对左子树的根节点进行操作时,需要将左子树根节点的左右子树存进容器,即在根节点右子树的后面。该容器选用队列。


3.二叉搜索树的后序遍历序列

题目描述:

输入一个非空整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence == null || sequence.length == 0){
            return false;
        }
        int count = sequence.length-1;
        return isRight(sequence, 0, count);
        }
    
        public boolean isRight(int [] sequence, int start, int count){
            if(start >= count){
                return true;
            }
            int root = sequence[count];
            int i = start;
            for(; i < count; ++i){
                if(sequence[i] > root){
                    break;
                }
            }
            int j = i;
            for(; j < count; ++j){
                if(sequence[j] < root){
                    return false;
                }
            } 
            return isRight(sequence, start, i-1) && isRight(sequence, i, count-1);
        }
}

相关知识点:

1.后序遍历顺序:左右根。二叉搜索树:如有左子树,则左子树小于根节点,如有右子树,右子树大于根节点;

2.根据数组的最后一个数字,与前面数字依次判断,当出现大于根节点的数字,其前面的数字都是左子树,再判断大于根节点之后的数字是否均大于根节点,若不是,则可直接判定,若是,则再对左右子树利用上述方法进行判断是否满足二叉搜索树。


四、矩阵

1.顺时针打印矩阵

题目描述:

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
      ArrayList<Integer> result = new ArrayList<Integer>() ;
        if(matrix==null || matrix.length==0) { return result ; }
 
        printMatrixClockWisely(matrix, 0, 0, matrix.length - 1, matrix[0].length - 1, result);
 
        return result ;
    }
    
    public void printMatrixClockWisely(int[][] matrix, int startRow, int startCol, int endRow, int endCol, ArrayList<Integer> result) {
        if(startRow<endRow && startCol<endCol) {
            for(int j=startCol; j<=endCol; j++) { result.add(matrix[startRow][j]) ; }   //Right
            for(int i=startRow+1; i<=endRow-1; i++) { result.add(matrix[i][endCol]) ; }     //Down
            for(int j=endCol; j>=startCol; j--) { result.add(matrix[endRow][j]) ; }     //Left
            for(int i=endRow-1; i>=startRow+1; i--) { result.add(matrix[i][startCol]) ; }   //Up
            printMatrixClockWisely(matrix, startRow + 1, startCol + 1, endRow - 1, endCol - 1, result) ;
        }else if(startRow == endRow && startCol < endCol){
            for(int j = startCol; j <= endCol; j++){
                result.add(matrix[startRow][j]);
            }
        }else if(startCol == endCol && startRow < endRow){
            for(int i = startRow; i <= endRow; i++){
                result.add(matrix[i][startCol]);
            }
        }else if(startRow == endRow && startCol == endCol){
            result.add(matrix[startRow][startCol]);
        }else{
            return ;
        }
    }
}

相关知识点:

1.注意判断多个边界条件;

2.学会借助图形理清思路


2.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值