Day3.牛客网剑指offer 67题之22-32题(java代码)

牛客网剑指offer 67题(22-32)

有时候觉得自己特别容易陷入一些死圈套,纠结半天,感觉自己的数学推理部分还是略菜啊,找不到好的思路突破点,慢慢来吧。

22.从上往下打印二叉树

题目描述
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
思路
二叉树的层次遍历,用广度优先搜索办法,这里并没有按层分开输出,所以实现又稍微简单一些,跟按层输出原理类似。

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

23.二叉搜索树的后序遍历

题目描述
输入一个非空整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
思路
后序遍历的顺序是左右根,根据二叉搜索树的性值,以最后一个元素即根结点为基准,找到前部不大于根结点的为左子树,大于根结点为右子树,即只要比较前半部分的数是否小于根结点和后半部分数是否大于根结点,并迭代判断左右子树。

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence.length==0) return false;
        return isTrue(sequence,0,sequence.length-1);
        
    }
    public boolean isTrue(int []sequence,int first,int last){
        if(first>=last) return true;
        int rootValue=sequence[last];
        int index=first;
        while(sequence[index]<=rootValue&&index<last){
            index++;
        }
        for(int i=index;i<last;i++){
            if(sequence[i]<rootValue)
                return false;
        }
        return isTrue(sequence,first,index-1) && isTrue(sequence,index,last-1);//分别迭代判断左右子树
    }
}

24.二叉树中和为某一的路径

题目描述
输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
思路
深度优先搜索的方法去遍历,减去每次遍历的值,如果等于0了则是一条路径(且当前结点是叶子结点,所以还要加上叶子结点的判断)。

public class Solution {
    private ArrayList<ArrayList<Integer>> resultPath=new ArrayList<>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        if(root==null) return resultPath;
        ArrayList<Integer> list=new ArrayList<>();
        FindPathDFS(root,target,list);
        return resultPath;
    }
    
    public void FindPathDFS(TreeNode node,int target,ArrayList<Integer> path){
        if(node==null) return;
        path.add(node.val);
        target-=node.val;
        if(target==0&&node.left==null&&node.right==null){
            resultPath.add(new ArrayList<>(path));
        }else if(target>0){
            FindPathDFS(node.left,target,path);
            FindPathDFS(node.right,target,path);
        }
        path.remove(path.size()-1);//移除当前已遍历的路径结点值
    }
}

25.复杂链表的复制

题目描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
思路
刚开始看到题目都没懂是什么意思,不过既然是复制,直接通过在相邻结点中加入一个新节点再将这些节点连接应该就是一个新链表了,但是那个特殊指针指向一个随机节点没有太明白意思,还是说他这个给定了随机节点指针,你只要判断它是否存在并且连接就好了?如果是这样的话应该就是可以分为三步,插入新节点,连接随机节点,拆分链表(将原有链表的指针断开)
(emmm悄悄地说一句要是有个示例可能会更好理解一些)
后面画了个图应该就是思路如下:
随便一画

public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead == null) return null;
        //生成新节点
        RandomListNode node=pHead;
        while(node!=null){
            RandomListNode newNode=new RandomListNode(node.label);//生成的节点是相邻节点前面的值
            newNode.next=node.next;
            node.next=newNode;
            node=newNode.next;//指向原链表的下一节点
        }
        //连接random节点
        node=pHead;
        while(node!=null){
            RandomListNode newNode=node.next;
            if(node.random!=null){
                newNode.random=node.random.next;//新链表的所有对应节点都是原链接节点的下一节点
            }
            node=newNode.next;
        }
        
        //拆分链表
        node=pHead;
        RandomListNode newHead=node.next;
        while(node.next!=null){
            RandomListNode newNode=node.next;
            node.next=newNode.next;
            node=newNode;
        }
        return newHead;
    }
}

26.二叉搜索树与双向链表

题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路
了解二叉搜索树和双向链表的结构性质就比较好写了,仍然是可以使用中序遍历。

public class Solution {
private TreeNode pre = null;//用于记录上一个结点
	private TreeNode head = null;
	
	public TreeNode Convert(TreeNode pRootOfTree) {
		if(pRootOfTree == null) return null;
		
		inOrder(pRootOfTree);
		
		return head;
    }
	
	private void inOrder(TreeNode node) {
		if(node == null) return;
		
		//实质上是中序遍历,改结点的指针。left代表双向链表的prev指针,right代表next
		inOrder(node.left);
		
		//改指针的指向(只需与上一个结点相连即可)
		node.left = pre;//连上一个
		if(pre != null) {//如果上一个不为null,连此时这个
			pre.right = node;
		}
		pre = node;//将pre移向此时这个结点,为下一次迭代做准备
		
		if(head == null) head = node;//只在第一次找到最小结点时作为头结点
		
		inOrder(node.right);
	}
}

27.字符串的排序

题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
思路
可以先让字符排序,递归回溯实现,每次回溯的时候要剔除掉当前循环加入的字符,回到上一步的排列。

import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
    private ArrayList<String> result = new ArrayList<>();
	
	public ArrayList<String> Permutation(String str) {
        if(str == null || str.length() == 0) return result;
		
		char[] chars = str.toCharArray();
		Arrays.sort(chars);//字典序排序
		
		permutation(chars,
				new boolean[chars.length],//用于记录当前字符是否用过
				new StringBuffer());//字符串,便于操作
		
		return result;
    }
	
    public void permutation(char[] chars, boolean[] hasUsed, StringBuffer str) {
		if(str.length() == chars.length) {//长度相同说明出结果,加入result
			result.add(str.toString());
			return;
		}
		
		for(int i = 0; i < chars.length; i++) {
			if(hasUsed[i]) continue;
	        if(i != 0 && chars[i] == chars[i - 1] && !hasUsed[i - 1]) continue;//连续两个值相同时,保证不重复
	        hasUsed[i] = true;
	        str.append(chars[i]);
	        
	        //递归对后面的字符进行排列
	        permutation(chars, hasUsed, str);
	        
	        //此步重要,去除此循环加入的字符,回退到上一步的排列,与T24中去除结点道理一样
	        str.deleteCharAt(str.length() - 1);
	        hasUsed[i] = false;
	        
		}
	}

}

28.数组中出现次数超过一半的数字

题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路
直接先对数组进行排序,用temp存储当前遍历位置的数字,然后执行循环如果统计某一个数字连续出现次数大于数组长度一半退出循环,输出temp,找不到就输出0(用for循环思路应该清晰一些)

import java.util.Arrays;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        Arrays.sort(array);
        int temp=array[0];
        int i=0,count=0;
        while(i<array.length){
            if(array[i]!=temp){
                count=0;
                temp=array[i];
            }
            count++;
            if(count>array.length/2) break;
            i++;
        }
        return count>array.length/2?temp:0;
    }
}

29.最小的k个数

题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
思路
emmm我承认之前直接调用sort方法有一些投机行为,不过直接使用确实很简单啊hhh。

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

其实这一题思路可以参考快排的算法,你甚至可以直接使用快排排序后再进行选择,对于快排的一个改进就是通过数组的第k个数字进行调整,使得比第k个数字小的所有数字位于数组左边,大的位于数组右边,最后调整后数组前k个数就是最小的k个数(它们之间不一定有序)。

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list=new ArrayList<>();
        if(input==null||k>input.length||k<=0) return list;
        int start=0,end=input.length-1;
        int index=partition(input,start,end);
        int target=k-1;
        while(index!=target){
            if(index<target){
                start=index+1;
            }else{
                end=index-1;
            }
            index=partition(input,start,end);
        }
        for(int i=0;i<k;i++){
            list.add(input[i]);
        }
        return list;
    }
    public int partition(int arr[],int left,int right){
        int i=left,j=right+1;//左右指针
        int pivot=arr[left];
        while(true){
            //从左边开始找比pivot大的数
            while(i<right&&arr[++i]<pivot){
                if(i==right) break;
            }
            //从右边找比pivot小的数
            while(j>left&&arr[--j]>pivot){
                if(j==left) break;
            }
            //两个指针相遇
            if(i>=j) break;
            //交换左边较大的和右边较小的数
            swap(arr,i,j);
        }
        swap(arr,left,j);
        return j;
    }
    public void swap(int []arr,int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
}

30.连续子数组的最大和

题目描述
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
思路
这个连续子数组的最大和是否包含负数其实并不影响最终值,我们用max存储当前最大值,用sum来存储当前子数组的和,如果sum小于0,那说明这一部分是一个负收益,只需要直接跳到下一位置重新计算即可。

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int sum=0,max=Integer.MIN_VALUE;
        for(int i=0;i<array.length;i++){
            if(sum>0)
                sum+=array[i];
            else
                sum=array[i];
            max=Math.max(max,sum);
        }
        return max;
    }
}

31.整数中(1-n)1出现的次数

题目描述
求出1-13的整数中1出现的次数并算出100-1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
思路
这种题就完全可以说是数学方法题,最简单也基本通过不了的方法就是穷举法了。。不过这种数学题如果没有一个好的思路真的特别容易被卡住,在这里给一个思路供大家学习。。。

    令a=n/i,b=n%i;
    举例分析,假设n=31456,i=100; 此时a=314,b=56:
        1)当a的个位大于1时(也就是n的百位),百位为1的数总共出现了(a/10+1)*100次
        2)当a为1的时候,百位为1的数总共出现了(a/10)*100+(b+1);
        3)当a为0的时候,百位为1的数出现了(a/10)*100次;
    因此可以根据a的个位是否为1分成2种情况计算,可以使用一个表达式合并以上三个式子。
   a.  9=>(a个位)>=2   和   0=<(a个位<=1)时的  (a/10+1)、(a/10)表达式与(a+8)/10
    等价;
   b.  (a>=2时(a+8)/10的结果与a/10+1相同,a==1或a==0时(a+8)/10的结果等价a/10。
   c.   当a个位为1时需要加上(b+1)与(a%10==1)*(b+1)等价。因此合并后百位为1的数的个数为
  (a+8)/10*i+(a%10==1)*(b+1);
    然后令i从个位到最高位遍历即可计算每一位含1的个数;
public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int cnt=0;
        for(long i=1;i<=n;i*=10){
            long a=n/i,b=n%i;
            cnt+=(a+8)/10*i+(a%10==1?(b+1):0);
        }
        return cnt;
    }
}

32.把数组排成最小的数

题目描述
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
思路
这里需要比较数字转化字符相加后的大小,两字符相加把小的那一部分放前面。

import java.util.Arrays;

public class Solution {
    public String PrintMinNumber(int [] numbers) {
        String[] nums = new String[numbers.length];
		for(int i = 0; i < nums.length; i ++) {//int转string,比较string相加的值
			nums[i] = String.valueOf(numbers[i]);
		}
		
		Arrays.sort(nums, (s1, s2) -> (s1 + s2).compareTo(s2 + s1));//排序,s1+s2与s2+s1两个字符串比较,谁小谁放前面
		String result = "";
		for(String str: nums) {
			result += str;
		}
		
		return result;
    }
}

下一篇: 牛客网剑指offer 67题之33-42题
上一篇: 牛客网剑指offer 67题之12-21题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值