剑指offer(26-33题)详解

欢迎关注个人数据结构专栏
剑指offer系列
剑指offer(1-10题)详解
剑指offer(11-25题)详解
剑指offer(26-33题)详解
剑指offer(34-40题)详解
剑指offer(51-59题)详解
剑指offer(34-40题)详解
微信公众号:bigsai
在这里插入图片描述
声明:大部分题基本未参考题解,基本为个人想法,如果由效率太低的或者错误还请指正!!

26 二叉搜索树与双向链表

题目描述

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

思路
关于二叉树搜索(排序树)需要我们转换成一个排序的双链表。我们知道二叉树有leftright。用来表示树的左右结构,但是它要求我们将树形结构转换成链式,并且还是有序的,并且还不能建立新的节点。二叉搜索树(排序树)我们知道它的中序序列是有序的,所以我们可以使用非递归的中序遍历,先想办法将节点按照中序的规则进行释放。根据释放的顺序,记录前一个节点互相记住左右就好了。

对于二叉树各种遍历以及递归非递归方式,笔者以前都梳理过,大家可以看看。

利用栈记录一下节点,用一个prenode记录前一个节点指针,当抛出当前节点两个节点左右互指以下就OK了。当然,要有个headnode记录第一个节点返回。
有使用递归版本的过也可以,可以自行尝试。
在这里插入图片描述
实现代码为:

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

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

    }

}
*/
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
		
		 Stack<TreeNode> q1 = new Stack();	
		 TreeNode preNode=null;//前
		 TreeNode headNode=null;
			while(!q1.isEmpty()||pRootOfTree!=null)
			{
				if (pRootOfTree!=null) {
					q1.push(pRootOfTree);
					pRootOfTree=pRootOfTree.left;
					
				}
				else {
					pRootOfTree=q1.pop();//弹出
					if(preNode==null){preNode=pRootOfTree;headNode=preNode;}
					else 
					{
						preNode.right=pRootOfTree;
						pRootOfTree.left=preNode;
					}
					preNode=pRootOfTree;
					pRootOfTree=pRootOfTree.right;
				}
			}
		return headNode;
	    }
}

27 字符串的排列

题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

思路
这题我不知道别人有啥方法,但是我的第一感觉就是全排列问题。当然我知道的全排列有两种实现方法。一种是dfs回溯法,另一种是普通递归+排序才能过这题,因为dfs的全排列是有序的,而普通递归交换的全排列是没序的。这题两者效率可能相差不大,但就全排列的序列而言。掌握递归的全排列效率更高。当然这也是笔者所采用的。至于全排列笔者以前虽然记录过但写的一般有兴趣可以参考其他讲解。两种方式实现全排列

当然,这题它可能有相同的字母,所以需要用个Hash去重下更好,但我懒直接用list判断存不存在了。

实现代码为:

import java.util.ArrayList;
public class Solution {
     public ArrayList<String> Permutation(String str) {
		 ArrayList<String> list=new ArrayList<String>();
		 char a[]=str.toCharArray();
		 arrange(a, 0, a.length - 1,list);
		 list.sort(null);
	     return list;
	    }

	private void arrange(char[] a, int start, int end, ArrayList<String> list) {
		// TODO Auto-generated method stub
		if (start == end) {	
			String team=getstr(a);
			if(!list.contains(team))list.add(getstr(a));
			return;
		}
		for (int i = start; i <= end; i++) {
			swap( i, start,a);
			arrange(a, start + 1, end,list);
			swap( i, start,a);
		}	
	}
	private String getstr(char[] a) {
		String str="";
		for(int i=0;i<a.length;i++)
			str+=a[i];
		return str;
	}
	private void swap(int i, int j, char a[]) {
		char team=a[i];
		a[i]=a[j];
		a[j]=team;	
	}
}

28 数字中出现次数超过一半的数字(待优化)★

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

思路
这题是一道经典题。我只知道有一个更好方法但是我还没消化。二轮吸收讨论区的时候肯定会加上的。而我采用笨方法就是hashmap存入判断。有则返回。这样就是开了更大的内存。这题后面还会优化。

实现代码为:

import java.util.HashMap;
import java.util.Map;
public class Solution {
  public int MoreThanHalfNum_Solution(int[] array) {
		if(array.length==1)return array[0];
		Map<Integer, Integer> map = new HashMap<Integer, Integer>();
		int len = array.length  / 2;
		int team = 0;
		for (int a : array) {
			if (map.containsKey(a)) {
				team = map.get(a);
				if (team >= len)
					return a;
				else {
					map.put(a, team + 1);
				}
			} else {
				map.put(a, 1);
			}
		}
		return 0;
	}
}

参考讨论区:
这题是经典面试题,很多情况会问不允许开更大的空间完成这个要求,这就要根据数据的特点来完成

如果有大于一半的数!那么假设所有数争夺一个猎物!同样的聚成群,不一样的1v1同归于尽,那么最后剩的一群或者一个那么就是大于一半的(如果存在一半的话)!

  • 在具体实现就是设两个数,一个数num等于遍历的数,另一个count就是出现的次数。
  • 如果遍历的数等于num,那么count++
  • 如果不等于,那么消灭一个num,count--,如果count被减成0,那么就换个数,同时count变成1.

实现代码为:

public int MoreThanHalfNum_Solution(int[] array) {
		int num=array[0];int count=0;
		for(int i=0;i<array.length;i++)
		{
			if(num==array[i])count++;
			else {
				if(--count==0)
				{
					num=array[i];
					count=1;
				}
			}
		}
		if(count==0)return 0;
		count=0;
		for(int i=0;i<array.length;i++)
		{
			if(num==array[i])count++;
		}
		if(count>array.length/2)
			return num;
		return 0;
	}

29 最小的K个数

题目描述

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

思路
排序取前K个数字,当然需要讨论下特殊情况。

实现代码:

import java.util.Arrays;
import java.util.ArrayList;
public class Solution {
   public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list=new ArrayList<Integer>();
        if(k>input.length)return list;
        Arrays.sort(input);
        for(int i=0;i<k;i++)
        {
        	list.add(input[i]);
        }
        return list;
    }
}

30 连续子数组最大和

题目描述

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

思路
dp动态规划问题问题,最长递增子序列(LIS),和最长公共子串(LCS)都是经典问题。此题大家可以了解LIS。至于这种dp问题,要掌握它们的初始化+动态方程,至于这个动态方程。用dp[i]表示第i个节点的最大序列和。

而本题的dp方程为:dp[i]=max(array[i],array[i]+dp[i-1])

我们不考虑整个串串,只考虑连个点之间的关系。1、串串连续 2、串串长度任意可以为一.对于第i个节点,他想要最长的连续序列。那么有两种情况。第一种就是和前面连在一起然后加上自己。当然前面的是多少我们不管,我们只知道前面的最大时dp[i-1].或者自立门户长度为1不和前面的接起来。当然这需要两者比较取最大。

在这里插入图片描述
实现代码为:

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int max=0;
        int dp[]=new int[array.length];
        dp[0]=array[0];max=dp[0];
        for(int i=1;i<array.length;i++)
        {
             
            dp[i]=max(dp[i-1]+array[i],array[i]);
            max=max>dp[i]?max:dp[i];
        }
        return max;
    }
    static int max(int a,int b)
    {
        return a>b?a:b;
    }
}

31 整数中1出现的次数

题目描述

求出1~ 13的整数中1出现的次数,并算出100~ 1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

思路
最笨的方法(可过)。使用字符串,将从1道n的字符串拼凑成新的字符串,然后遍历查找1就可以了。至于数学方法的话当初想了一会感觉考虑点挺多,后面还会再想想。

实现代码:

public class Solution {
   	 public  int NumberOf1Between1AndN_Solution(int n) {
		  StringBuilder str=new StringBuilder();
		 for(int i=1;i<=n;i++)
		 {
			 str.append(i);
		 }
		 int va=0;
		 for(int i=0;i<str.length();i++)
		 {
			 if(str.charAt(i)=='1')va++;
		 }
		 return va;
	    }
}

参考讨论区:
用数学方法效率更高,不过这个规律还是得经验,,,我还是有点菜当时想了一段时间总是bug。。
在这里插入图片描述

32 把数组排成最小的数

题目描述

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

思路
贪心算法,主要是一种贪心思想。其实也是个变形排序,其实你不用考虑具体的贪心思想是什么,你要保证整个序列排成的数字最小。那么你就要保证任意两个数字排序的相对数字最小哦。,本质是需要我们考虑最小前缀和组合的问题,但是对于任意两个数,在比较接口中你直接拼凑比较就可以了。但是如果有更好方法后面会学习。

实现代码:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
public class Solution {
    public String PrintMinNumber(int [] numbers) {
		Integer[] nums=new Integer[numbers.length];//赋值
		for(int i=0;i<numbers.length;i++)
		{
			nums[i]=numbers[i];
		}
		 Arrays.sort(nums, cmp);
		 StringBuilder str=new StringBuilder();
		 for(int i=0;i<nums.length;i++)
		 {
			 str.append(nums[i]);
		 }
		 return str.toString();
	    }
	 static Comparator<Integer>cmp=new Comparator<Integer>() {

		public int compare(Integer o1, Integer o2) {
			//51 3551
			//921 3
			//51 515
			String a1=o1+"";
			String a2=o2+"";
			return Integer.parseInt(a1+a2)-Integer.parseInt(a2+a1);
		}
	};
}

33 丑数★

题目描述

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

思路
这题自己的方法只过了87%的样例。当数据大的时候就凉了。这题想了一段时间最终忍不住看了题解发现真的太鸡儿秒了!

先说说我自己的想法,怎么想的,他首先没说数据范围是啥,所以我一直以为这个会是一个慢慢试探的过程。

  1. 刚开始尝试的肯定是暴力。他说第N个,那我我就一个个试。每个数除以2、3、5直到无法除看看找到第n个。这种最暴力也最好想。我尝试了但是失败了。
  2. 我知道任何数都能实现质因数分解。也就是这个丑数要满足(2a*3b*5c)。我抱着侥幸尝试用三层循环(剪枝优化一下次数),将(2a*3b*5c)的所有可能性全部添加到集合中然后排个序取出。但是结果依然爆炸。
  3. 根据第二种的进行优化。我是这样想的,这个序列我们是不是可以用一个优先队列来维护它?比如将1放进去,然后抛出的元素放入到hashset中去重。每次抛出的数值m将m * 2,m * 3, m * 5放入优先队列,然后再抛最小的出来加入set。直到set中元素满了为止。但是面对巨大的数字依然失败了。因为占据大空间,思想没错,但是巨大重复!!

整个过程数组这个结构都被我忽略了!!!!实在hold不出来,忍不住去参考了下讨论区发现这个方法真的是太好了。其实思想还是跟我上面的很相似,只是结构用了最优结构——数组!

简单来说就是用了三个光标,a2,a3,a5.每次比较三个位置分别*2,*3,*5谁最小,然后对应光标往右移动一位。!到结束即可!

看个图就理解了:
在这里插入图片描述
实现代码为:

import java.util.*;
public class Solution {
public static int GetUglyNumber_Solution(int index) {
		int a2=0,a3=0,a5=0;
		if(index<7)return index;//小于7都是
		List<Integer>list=new ArrayList<Integer>();
		list.add(1);
		for(int i=1;i<index;i++)
		{
			int min=Math.min(list.get(a2)*2, Math.min(list.get(a3)*3, list.get(a5)*5));
			list.add(min);
            if(min==list.get(a2)*2)a2++;
			if(min==list.get(a3)*3)a3++;
			if(min==list.get(a5)*5)a5++;
		}
		return list.get(index-1);
	}	
}
发布了196 篇原创文章 · 获赞 1879 · 访问量 75万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章