《剑指offer》66道题详解第二篇

本文精选了38道经典算法题目,覆盖数值运算、链表操作、字符串处理、二叉树遍历、矩阵运算等核心算法知识,每道题目附有详细解析及代码实现,旨在帮助读者深入理解算法设计思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

16、数值的整数次方
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

思路:
(1)当指数为负数的时候:可以先对指数求绝对值,然后算出次方的结果之后再取倒数。
(2)当底数(base)是零且指数是负数的时候:通过全局代码或异常告诉调用者参数有误。
(3)0的0次方的时候:由于0的0次方在数学上是没有意义的,因此无论是输出0还是1都是可以接受的。
(4)在这里插入图片描述

public class Solution {
    public double Power(double base, int exponent) {
          if(exponent == 0)
            	return 1;
            else if(exponent == 1)
            	return base;
            else{
            	int flag = -1;
            	double result=0.0;
            	if(exponent < 0){
            		flag = 1;
            		exponent = -exponent;
            	}
            	
            	if (exponent > 0) {
					if((exponent & 1) == 1){//说明是奇数
						result = Power(base, exponent/2);
						result = result*result*base;
					}
					else{
						result = Power(base, exponent/2);
						result =result *result;
					}
				}
            	
            	if(flag == 1)
            		return 1/result;
            	else
            		return result;
            	
            }
  }
}

17、删除链表中重复的节点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

思路:就是删除重复的结点,需要注意的是怎样处理第一个节点就是重复结点的情况,解决方法是再建立一个头结点first。


public static ListNode deleteDuplication(ListNode pHead) {
         
        ListNode first = new ListNode(-1);//设置一个trick
 
        first.next = pHead;
 
        ListNode p = pHead;
        ListNode last = first;
        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;
    }

18、正则表达式匹配

请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表
示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整
个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配

思路:



/*
    解这题需要把题意仔细研究清楚,反正我试了好多次才明白的。
    首先,考虑特殊情况:
         1>两个字符串都为空,返回true
         2>当第一个字符串不空,而第二个字符串空了,返回false(因为这样,就无法
            匹配成功了,而如果第一个字符串空了,第二个字符串非空,还是可能匹配成
            功的,比如第二个字符串是“a*a*a*a*”,由于‘*’之前的元素可以出现0次,
            所以有可能匹配成功)
    之后就开始匹配第一个字符,这里有两种可能:匹配成功或匹配失败。但考虑到pattern
    下一个字符可能是‘*’, 这里我们分两种情况讨论:pattern下一个字符为‘*’或
    不为‘*’:
          1>pattern下一个字符不为‘*’:这种情况比较简单,直接匹配当前字符。如果
            匹配成功,继续匹配下一个;如果匹配失败,直接返回false。注意这里的
            “匹配成功”,除了两个字符相同的情况外,还有一种情况,就是pattern的
            当前字符为‘.’,同时str的当前字符不为‘\0’。
          2>pattern下一个字符为‘*’时,稍微复杂一些,因为‘*’可以代表0个或多个。
            这里把这些情况都考虑到:
               a>当‘*’匹配0个字符时,str当前字符不变,pattern当前字符后移两位,
                跳过这个‘*’符号;
               b>当‘*’匹配1个或多个时,str当前字符移向下一个,pattern当前字符
                不变。(这里匹配1个或多个可以看成一种情况,因为:当匹配一个时,
                由于str移到了下一个字符,而pattern字符不变,就回到了上边的情况a;
                当匹配多于一个字符时,相当于从str的下一个字符继续开始匹配)
    之后再写代码就很简单了。
*/
public class Solution {
     public boolean match(char[] str, char[] pattern)
    {
        if(str==null || pattern ==null)
        	return false;
        int strIndex = 0;
        int patternIndex = 0;
        
        return doMatch(str,strIndex,pattern,patternIndex);
    } 
  public boolean doMatch(char[] str,int strIndex, char[] pattern,int patternIndex){
        if(strIndex == str.length && patternIndex==pattern.length)
        	return true;
        if(strIndex!=str.length && patternIndex==pattern.length)
        	return false;
       //模式第2个是*,且字符串第1个跟模式第1个匹配,分3种匹配模式;
        if(patternIndex+1<pattern.length && pattern[patternIndex+1] == '*'){ 
        	if((strIndex!=str.length && pattern[patternIndex] == str[strIndex]) 
        			|| (pattern[patternIndex] == '.' && strIndex!=str.length)){//如果有*,且可能会匹配,则有三种情况
        		return doMatch(str, strIndex+1, pattern, patternIndex+2) //匹配str的1个字符
        				|| doMatch(str, strIndex+1, pattern, patternIndex)//匹配str的多个字符
        	            || doMatch(str, strIndex, pattern, patternIndex+2);//匹配str的0个字符
        	}else{//如果有*,但是不匹配
            	return doMatch(str, strIndex, pattern, patternIndex+2);
            }
        }
        //模式第2个不是*,且字符串第1个跟模式第1个匹配,则都后移1位,否则直接返回false
        if((strIndex!=str.length && pattern[patternIndex] == str[strIndex]) 
        		|| (pattern[patternIndex]=='.' && strIndex != str.length)){
        	return doMatch(str, strIndex+1, pattern, patternIndex+1);
        }
        return false;
        
    }
}

19、表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。

思路:表示数值的字符串遵循模式A[.[B]][e|EC] | .B[e|EC]。其中A是数值的整数部分,B是数
值的小数部分,C是数值的整数部分,e和E是终结符表示带科学计数法(10的几次幂),A和C能
以终结符+和-开头,也可以像B一样是普通的数位串。
判断一个字符串是否符合上述模式,首先尽可能多的扫描0~9的数位(有可能在起始处有+或者-),也就是A部分,如果遇到小数点,则开始扫描B部分,如果遇到E或者e,就开始扫描C部分
public class Solution {
   
	static int count;
	public static boolean isNumeric(char[] str) {
      count=0;
		boolean abIsInterger = true;
		boolean eIsInterger = true;
		boolean bIsInterger =true;
		// 检查A是否是有符号的整数数值
		boolean aIsInterger = isInterger(str);
		if (count<str.length &&str[count] == '.') {
			++count;
			bIsInterger = isUnsignedInterger(str);
		}
		// 数字可以有整数但是没小数,也可以有小数但是没整数,也可以有效数有整数	
		abIsInterger = aIsInterger || bIsInterger;
		if (count<str.length && (str[count] == 'e' || str[count] == 'E')) {
			++count;
			eIsInterger = isInterger(str);
		}

		return (count==str.length) && abIsInterger && eIsInterger;

	}

	public static boolean isUnsignedInterger(char[] str) {

		int countBefore = count;
		while (count < str.length && str[count] >= '0' && str[count] <= '9')
			++count;
		// 如果小数点前有数,则返回true,小数点前没有数字,返回false
		return count > countBefore;
	}

	public static boolean isInterger(char[] str) {

		if (count<str.length && (str[count] == '+' || str[count] == '-')) {
			++count;
		}

		return isUnsignedInterger(str);

	}
}

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

链接:https://www.nowcoder.com/questionTerminal/beb5aa231adc45b2a5dcc5b62c93f593
来源:牛客网

/**
 * 1.要想保证原有次序,则只能顺次移动或相邻交换。
 * 2.i从左向右遍历,找到第一个偶数。
 * 3.j从i+1开始向后找,直到找到第一个奇数。
 * 4.将[i,...,j-1]的元素整体后移一位,最后将找到的奇数放入i位置,然后i++。
 * 5.終止條件:j向後遍歷查找失敗。
 */
public class Solution {
    public void reOrderArray(int [] array) {
       int pre = 0;
		int after = 1;
		for (; after < array.length;) {
			if (array[pre] % 2 == 0) {// 如果是偶数,就找后面的距离最近的奇数
				while ((after < array.length) && (array[after] % 2 == 0)) {
					after++;
					if (after > array.length)
						break;
				}

				if (after < array.length) {
					int tempdata = array[after];// 将奇数存下了
					int tempcount = after;// 将after这个位置存下了
					for (int j = 0; j < after - pre; j++) {
						array[tempcount] = array[--tempcount];
					}
					array[pre] = tempdata;
				}

			}
			pre++;
			after++;
		}

		for (int i = 0; i < array.length; i++) {
			System.out.println(array[i]);
		} 
    }
}

如果不需要保证保证奇数和奇数,偶数和偶数之间的相对位置不变,则可以借鉴快速排序的做法。

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

思路:要注意输入为空指针,链表的结点少于k,k为0的情况

/*
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 p = head;
	        ListNode q = p;
	        for(int i=0;i<k;i++){
	        	if(q!=null)
	        		 q = q.next;
	        	else
	        		return null;
	           
	        }
	        while(q!=null){
	            p=p.next;
	            q=q.next;
	        }
	        return p;
	       
	    }
}

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

思路:
第一步:确定是否有环
可以申请两个指针,一个指针一次走一步,一个指针一次走两步,如果走得快的指针追上了走得慢的指针,那么链表就有环
第二步:找到环的入口,申请两个指针,加入环中有n个结点,则第一个指针先走n个结点,然后两个指针一起走,当第二个指针指向环的入口时,第一个指针已经绕着环走了一圈,又回到了环的入口,此时两个指针相遇。
第三步:确定环中到底有几个结点。在第一步中,两个指针相遇则说明有环,此时让第一个指针继续向后走,并且计数,当该指针再次回到第二个指针所在的位置时,就有走完了一圈,此时计数就是环的节点数。

/*
 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 || pHead.next==null || pHead.next.next ==null)
    	   return null;
		
		ListNode temp1 =  pHead;
        ListNode temp2 =  pHead;
        
        temp1 =temp1.next;
    	temp2 =temp2.next.next;
        int count=0;
        while(temp1 != temp2){
        	temp1 =temp1.next;
        	if(temp2.next == null || temp2.next.next ==null)
        		return null;
        	temp2 =temp2.next.next;
        }
        temp1 =temp1.next;
        count++;
        while(temp1!=temp2){
        	temp1=temp1.next;
        	count++;
        }
        
        temp1 =pHead;
        temp2 =pHead;
        for(int i=0;i<count;i++){
        	temp2 = temp2.next;
        }
        
        while(temp1!=temp2){
        	temp1=temp1.next;
        	temp2=temp2.next;
        }
        return temp1;
    }
}

23、反转链表
输入一个链表,反转链表后,输出新链表的表头。
思路:使用三个指针就可以解决。
但是需要注意几个情况:
1、输入的链表是空的
2、输入的链表只有一个结点
3、输入的链表有多个节点

/*
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 null;
        ListNode prehead = new ListNode(0);
        ListNode temp = head;
        ListNode tempafter = temp.next;
        while (temp != null) {
            temp.next = prehead.next;
            prehead.next = temp;
            temp = tempafter;
            if(temp!=null)
            tempafter = temp.next;
        }
        return prehead.next;
    }
}

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

思路:
当两个链表都不为空时,比较两个链表的结点,数值小的结点加入已合并好的链表中。
当有一个链表为空时,将另一个链表全部加入合并好的链表中。

需要注意:
1、一个链表是空链表的情况
2、两个链表都是空链表的情况

/*
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)&&(list2==null))
            return null;
        else if (list1==null)
            return list2;
        else if (list2 ==null)
            return list1;
        else {
               ListNode templist1 = list1;
               ListNode templist2 = list2;
               ListNode head = new ListNode(0);
               ListNode rear = head;
               while((templist1!=null) && (templist2!=null)){
                    if(templist1.val <= templist2.val){
                        rear.next = templist1;
                        templist1=templist1.next;
                    }
                    else{
                        rear.next=templist2;
                        templist2=templist2.next;
                    }
                    rear=rear.next;        
                }
               if(templist1==null)
                   rear.next = templist2;
               else
                   rear.next = templist1;
               return head.next;
        }
        
    }
}

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

思路:先序遍历二叉树A,如果目前遍历的A的结点temp和B的根节点一致,则判断temp和B的子节点是否一致

注意:空指针的情况

/**
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;
		boolean result = false;
		if(root1.val == root2.val)
			result = IsSubtree(root1, root2);
		if(!result)
			result = IsSubtree(root1.left, root2);
		if(!result)
			result = IsSubtree(root1.right, root2);
		

	    return result;
	}
	
	public boolean IsSubtree(TreeNode temproot1,TreeNode root2){
		if(root2 == null)
			return true;
		if(temproot1 == null)
			return false;
		if(temproot1.val != root2.val)
			return false;
		return( IsSubtree(temproot1.left,root2.left) && IsSubtree(temproot1.right,root2.right));
	}
}

26、二叉树的镜像
操作给定的二叉树,将其变换为源二叉树的镜像。
在这里插入图片描述
思路:先序遍历二叉树的每个节点,如果遍历到的结点有子节点,就交换它的两个子节点。当交换玩所有的非叶子节点的左、右子节点后,就得到了树的镜像。
注意:用后续也可以,但是不能用中序!

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
 
    public TreeNode(int val) {
        this.val = val;
 
    }
 
}
*/
public class Solution {
    public void Mirror(TreeNode root) {
        if(root==null)
            return;
        DoMirror(root);
        if (root.left != null)
            Mirror(root.left);
        if (root.right != null)
            Mirror(root.right);
 
    }
 
    public void DoMirror(TreeNode root) {
        if (root != null) {
            TreeNode temp = root.left;
            root.left = root.right;
            root.right = temp;
        }
 
    }
}

27、对称的二叉树
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
思路:判断根节点的左右子树的根节点是否相同,再判断左子树的左子树和右子树的右子树是否相同以及 左子树的右子树和右子树的左子树是否相同,用递归即可。

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
 
    public TreeNode(int val) {
        this.val = val;
 
    }
 
}
*/
public class Solution {
    boolean isSymmetrical(TreeNode pRoot) {
         return isSymmetrical(pRoot,pRoot);
    }
     
    boolean isSymmetrical(TreeNode pLRoot,TreeNode pRRoot){
        if(pLRoot==null && pRRoot ==null)
            return true;
        if(pLRoot==null || pRRoot==null)
            return false;
        if(pLRoot.val == pRRoot.val)
            return isSymmetrical(pLRoot.left, pRRoot.right) && isSymmetrical(pLRoot.right, pRRoot.left);
         
        else
            return false;
         
         
    }
}

28、顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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.

思路:这个问题的主要难点在于分析顺时针打印时的边界问题。
分析如下:
第一圈打印的第一个元素在矩阵中的位置是(0,0),第二圈打印的第一个元素在矩阵中的位置是(1,1),故每一圈打印的第一个元素都是(X,X),而且我们可以发现,让顺时针循环打印继续的条件是columns>2X,rows>2X,其中columns是总列数,rows是总行数。

分析结果如下:
顺时针循环继续的条件:columns>2X,rows>2X
边界:我们约定在做顺时针打印的时候,第一步是从左到右,第二步是从上到下,第三步是从右到左,第四步是从下到上。
那么:
进行第二步的前提是终止行号大于起始行号
进行第三步的前提是圈内至少有两行两列,即终止行号大于起始行号,且终止列号大于起始列号
进行第四步的前提是至少有三行两列,即终止行号比起始行号至少大2,同事终止列号大于起始列号。

import java.util.ArrayList;
public class Solution {
   ArrayList<Integer> list = new ArrayList<Integer>();
 
    public ArrayList<Integer> printMatrix(int[][] matrix) {
 
        int row = matrix.length;
        int col = matrix[0].length;
        if (row == 0 || col == 0)
            return null;
        int start = 0;
        for (; (row > (start * 2)) && (col > (start * 2)); start++) {
            doPrintMatrix(matrix, row, col, start);
        }
        return list;
    }
 
    public void doPrintMatrix(int[][] matrix, int row, int col, int start) {
        int endx = row - start - 1;
        int endy = col - start - 1;
        // 将第一行加入list
        for (int i = start; i <= endy; i++) {
            list.add(matrix[start][i]);
        }
        // 将最后一列加入list
        if (endx > start) {
            for (int i = start+1; i <= endx; i++)
                list.add(matrix[i][endy]);
        }
        // 将最后一行加入list
        if ((endy > start)&&(endx>start)) {
            for (int i = endy-1; i >= start; i--)
                list.add(matrix[endx][i]);
        }
 
        // 将第一列加入list
        if ((endx > (start + 1))&&(endy>start)) {
            for (int i = endx-1; i >= start+1; i--) {
                list.add(matrix[i][start]);
            }
        }
 
    }
}

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

思路:最开始的思路是,把栈中最小的元素存放在存放在成员变量中。如果栈中新加进来一个元素,那么把新元素和这个成员变量的值进行比较,然后把最小的值再存放在这个成员变量中。但是这样存储有一个问题,就是如果最小值出栈后,如果获取新的最小值?
所以考虑问题还是要更全面。

我们可以把每次最小的元素放到一个辅助栈中:
以下列例子为例:

首先往空栈里压入数字3,显然3是当前最小值,把3也压入辅助栈中。接下来压入4,发现4大于之前的最小值,因此把4压入数据栈后,往辅助栈里继续压入最小值3。第三步,往数据栈里压入2,由于2小于之前的最小值3,所以把2压入数据栈后,需要在辅助栈中压入数字2,同样压入数字1的时候,也要更新最小值,并把1压入辅助栈。
在这里插入图片描述
从上表可以看出,如果每次都把最小元素压入辅助栈,那么就能保证辅助栈的栈顶一直都是最小元素。当最小元素从数据栈内被弹出时,辅助栈的新栈顶元素就是下一个最小值。当第五步弹出数据栈的1之后,我们会把辅助栈的栈顶也弹出,这样,辅助栈的栈顶元素2就是新的最小元素。接下来继续弹出数据栈和辅助栈的栈顶元素,可以发现,每次弹出后,辅助栈的栈顶元素都是栈里的最小元素

import java.util.Stack;
 
public class Solution {
    private int minnumber = Integer.MAX_VALUE;
    Stack<Integer> numberStack = new Stack<Integer>();
    Stack<Integer> minStack = new Stack<Integer>();
    public void push(int node) {
        if(numberStack.empty()){
            minStack.push(node);
            minnumber = node;
        }
        else if(node <= minnumber){
            minStack.push(node);
            minnumber = node;
        }
         
        numberStack.push(node);
    }
 
    public void pop() {
        if(!numberStack.empty()){
             if(numberStack.pop() == minnumber){
                 minStack.pop();
                 minnumber = minStack.peek();
             }
              
        }
    }
 
    public int top() {
         return numberStack.peek();
    }
 
    public int min() {
          return minnumber;
    }
}

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

思路:
我们可以利用一个栈来模拟压栈和出栈
如果下一个弹出的数字刚好是栈顶数字,那么直接弹出;
如果下一个弹出的数字不是栈顶数字,那么把压栈序列中还没有入栈的数字压入栈中,知道把下一个需要弹出的数字压入到栈顶;
如果所有的数字都压入了栈,但是还没有找到下一个应该弹出的数字,那么这个序列就不可能是一个弹出序列。

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public boolean IsPopOrder(int[] pushA, int[] popA) {

		Stack<Integer> stack = new Stack<Integer>();
		int i = 0;
		int j = 0;
		for (; (i < pushA.length) && (j < popA.length); i++) {

			stack.push(pushA[i]);
			while (popA[j] != stack.peek()) {
				i++;
				if (i >= popA.length)
					break;
				stack.push(pushA[i]);
			}
			while ((!stack.empty()) && stack.peek() == popA[j]) {
				stack.pop();
				j++;
			}

		}
		return stack.empty();
	}
}

31、从上到下打印二叉树
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
我们可以利用队列

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
/**
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) {


		Queue<TreeNode> queue = new LinkedList<TreeNode>();
		ArrayList<Integer> arrayList = new ArrayList<Integer>();
if (root == null)
			return arrayList;
		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;
	}
}

32、把二叉树打印成多行
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

思路:这道题和按行打印二叉树类似,我们需要在按行打印二叉树的基础上加一个变量:结点的层数

import java.util.ArrayList;


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

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

    }

}
*/
public class Solution {
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
    ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>();
		int depth = 1;//层数
		AddTreeNode(list,depth,pRoot);
		return list;
    }
  
  public  void AddTreeNode(ArrayList<ArrayList<Integer>> list, int depth,TreeNode root){
		if(root==null)
			return;
		//出现新层的结点,就给list加一层
		if(depth>list.size())
			list.add(new ArrayList<Integer>());
		list.get(depth-1).add(root.val);
		AddTreeNode(list, depth+1, root.left);
		AddTreeNode(list, depth+1, root.right);
	}
    
}

33、之字形打印二叉树
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

思路:可以按行把结点存储在ArrayList<ArrayList> list中,然后奇数行从左到右输出,偶数行从右到左输出。

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

思路:后序遍历的序列中,最后一个数字就是根节点,且由于这棵树是二叉搜索树(就是二叉排序树),故数组中前面的数字(即不包含根节点的其他数字)可以分为两个部分:第一部分是左子树节点的值,他们都比根节点的值小,第二部分是右子树结点的值,他们的值都比根节点的值大。
所以我们可以递归判断:是不是每个子树都满足以上条件,若每个子树都满足以上条件,那么这个序列就是一个二叉搜索树的后序遍历的序列。

public class Solution {
   	public boolean VerifySquenceOfBST(int[] sequence) {

		if(sequence.length ==0)
			return false;
		if(sequence.length == 1)
			return true;
		return DoVerifySquenceOfBST(sequence, 0, sequence.length-1);
	}
	
	public boolean DoVerifySquenceOfBST(int[] sequence,int start,int end){
		if(start>=end)
			return true;
		int root = sequence[end];
		int i;
		for(i=start;i<end;i++){
			if(sequence[i]>root)
				break;
		}
		
		for(int j=i;j<end;j++){
			if(sequence[j]<root)
				return false;
		}
		
		return ((DoVerifySquenceOfBST(sequence, start, i-1)) && DoVerifySquenceOfBST(sequence, i, end-1));
	}
}

35、二叉树和为某一值的路径
输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

思路:用前序遍历的方式访问到某一节点时,我们把该节点添加到路径上,并类加该节点的值。
如果该节点为叶结点,并且路径中节点的值刚好等于输入的整数,则当前路径符合要求,我们把它打印出来。
如果当前节点不是叶节点,则继续访问它的节点。
当前结点访问结束后,递归函数将自动回到它的父节点,因此我们在函数退回之前要在路径上删除当前节点并且减去当前结点的值,以确保返回父节点时的路径正好是从根节点到父节点。

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

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

    }

}
*/
public class Solution {
   ArrayList<ArrayList<Integer>> arraylistALL = new ArrayList<ArrayList<Integer>>();
	ArrayList<Integer> arrayList = new ArrayList<Integer>();

	public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
		if (root == null)
			return arraylistALL;
		target -= root.val;
		arrayList.add(root.val);
		if ((target == 0) && (root.left == null) && (root.right == null))
			arraylistALL.add(new ArrayList<Integer>(arrayList));
		FindPath(root.left, target);
		FindPath(root.right, target);
		arrayList.remove(arrayList.size() - 1);
		return arraylistALL;
	}
}

36、复杂链表的复制
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

思路:
第一步仍然是根据原始链表的每个结点N创建对应的N’。(把N’链接在N的后面)
在这里插入图片描述
第二步设置复制出来的结点的Sibling。(把N’的Sibling指向N的Sibling)
在这里插入图片描述
第三步把这个长链表拆分成两个链表:把奇数位置的结点用Next链接起来就是原始链表,偶数数值的则是复制链表。
在这里插入图片描述

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
 public RandomListNode Clone(RandomListNode pHead) {

		CloneNode(pHead);
		CloneRandomLink(pHead);
		return ReconnectNodes(pHead);
	}

	public void CloneNode(RandomListNode pHead) {
		RandomListNode pNode = pHead;
		while (pNode != null) {
			RandomListNode cloneNode = new RandomListNode(0);
			cloneNode.label = pNode.label;
			cloneNode.next = pNode.next;
          cloneNode.random =null;
			pNode.next = cloneNode;
			pNode = cloneNode.next;
		}
	}

	/*public void CloneRandomLink(RandomListNode pHead) {
		RandomListNode pNode = pHead;
		RandomListNode cloneNode = pNode.next;
		while (pNode != null) {
			
			cloneNode.random = pNode.random.next;
			pNode = cloneNode.next;
			cloneNode = pNode.next;
		}
	}*/
	public void CloneRandomLink(RandomListNode pHead) {
		RandomListNode pNode = pHead;

		while (pNode != null) {
			RandomListNode cloneNode = pNode.next;
			if (pNode.random != null)
				cloneNode.random = pNode.random.next;
			pNode = cloneNode.next;

		}
	}

	public RandomListNode ReconnectNodes(RandomListNode pHead) {
		RandomListNode pNode = pHead;
		if (pHead == null)
			return pNode;
		RandomListNode cloneStartNode = pHead.next;
		RandomListNode cloneNode = cloneStartNode;

		while (pNode != null) {
			pNode.next = cloneNode.next;
			pNode = cloneNode.next;
			if (pNode != null) {
				cloneNode.next = pNode.next;
				cloneNode = pNode.next;
			}

		}
		return cloneStartNode;
	}
}

37、二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

因为要将二叉搜索树转换成排序的双向链表,所以用中序遍历,且将二叉树的左指针和右指针作为前后指针。

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

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

    }

}
*/
public class Solution {
  	 TreeNode pre=null;
	 TreeNode preresult=null;
	public  TreeNode Convert(TreeNode root){
		
		DOConvert(root);
		return preresult;
	}
	

	public  void DOConvert(TreeNode root) {
		if (root != null) {
			DOConvert(root.left);
            if(pre!=null){
            	root.left = pre;
		    	pre.right = root;
            } 
			else
				preresult =root;	
			pre=root;
			
			DOConvert(root.right);
		}	
	}
}

38、序列化二叉树
可以用扩展先序表示二叉树,然后将扩展先序序列化(.tostring)和反序列化(string.split())

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

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

    }

}
*/
public class Solution {
  StringBuffer stringBuffer;
	String Serialize(TreeNode root) {
		stringBuffer = new StringBuffer("");
		doSerialize(root);
		//stringBuffer.deleteCharAt(stringBuffer.length()-1);
		return stringBuffer.toString();
         
	}
	void doSerialize(TreeNode root){
		if(root == null){
			stringBuffer.append("#");
			stringBuffer.append(",");
		}else{
        	stringBuffer.append(root.val);
        	stringBuffer.append(",");
    		doSerialize(root.left);
    		doSerialize(root.right);
        }
	}

	int count =-1;
	TreeNode Deserialize(String str) {
		if(str == null)
        	return null;
		TreeNode tempTreeNode = null;
		String[] array = str.split(",");
		tempTreeNode = doDeserialize(array,tempTreeNode);
		return tempTreeNode;
        
	}
	TreeNode doDeserialize(String[] array,TreeNode tempTreeNode){
		count++;
		if(count<array.length)
		{
			//if(array[count] == "$")
			if(array[count].equals("#"))
				tempTreeNode = null;
			else {
				tempTreeNode = new TreeNode(0);
				tempTreeNode.val = Integer.valueOf(array[count]);
					
				tempTreeNode.left = doDeserialize(array, tempTreeNode.left);
				tempTreeNode.right = doDeserialize(array, tempTreeNode.right);
				return tempTreeNode;
				
			}	
		}
		return null;	
	}
}

39、字符串的排列
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
https://www.cnblogs.com/cxjchen/p/3932949.html
思路:
链接:https://www.nowcoder.com/questionTerminal/fe6b651b66ae47d7acce78ffdd9a96c7?f=discussion
来源:牛客网

对于无重复值的情况
*
* 固定第一个字符,递归取得首位后面的各种字符串组合;
* 再把第一个字符与后面每一个字符交换,并同样递归获得首位后面的字符串组合; *递归的出口,就是只剩一个字符的时候,递归的循环过程,就是从每个子串的第二个字符开始依次与第一个字符交换,然后继续处理子串。
*
* 假如有重复值呢?
* *由于全排列就是从第一个数字起,每个数分别与它后面的数字交换,我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这两个数就不交换了。
* 例如abb,第一个数与后面两个数交换得bab,bba。然后abb中第二个数和第三个数相同,就不用交换了。
* 但是对bab,第二个数和第三个数不 同,则需要交换,得到bba。
* 由于这里的bba和开始第一个数与第三个数交换的结果相同了,因此这个方法不行。
*
* 换种思维,对abb,第一个数a与第二个数b交换得到bab,然后考虑第一个数与第三个数交换,此时由于第三个数等于第二个数,
* 所以第一个数就不再用与第三个数交换了。再考虑bab,它的第二个数与第三个数交换可以解决bba。此时全排列生成完毕!
*
*

import java.util.ArrayList;
import java.util.Collections;


public class Solution {
	public static ArrayList<String> Permutation(String str) {
		
		ArrayList<String> resultlist = new ArrayList<String>();
		if(str.length()==0)
			return resultlist;
		
		DoPermutation(str.toCharArray(),0,resultlist);
        Collections.sort(resultlist);
		return resultlist;
	}
	
	public static void DoPermutation(char[] str,int i,ArrayList<String> resultlist){
		if(i==str.length-1){
			String tempstr = new String(str);
			if(!resultlist.contains(tempstr)){
				resultlist.add(tempstr);
			}
		}
		
		else{
			for(int k=i;k<str.length;k++){
				char temp = str[i];
				str[i] = str[k];
				str[k] = temp;
				
				DoPermutation(str,i+1,resultlist);
				
				temp = str[i];
				str[i] = str[k];
				str[k] = temp;		
			}
		}
			
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值