【刷题笔记/剑指Offer】31—40

13 篇文章 0 订阅

1. 丑数

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

首先重点在于理解丑数的概念:就是说只能被2或3或5整除,直到整除到结果为1,就是丑数。

接下来,来分析获取第N个丑数的方法。简单的思路就是从1开始,往后每次检查该数是不是丑数,但是这样时间效率很差,考虑因子只能为2/3/5,这样从反向考虑问题,只要每次将因子乘以2/3/5,取其中的最小值,该数一定为丑数,代码如下:

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index <= 0)
            return 0;
        int[] uglyNumber = new int[index];
        uglyNumber[0] = 1;
        int index2 = 0;
        int index3 = 0;
        int index5 = 0;
        int curr = 1;
        while(curr < index) {
            int min = getMin(uglyNumber[index2] * 2, uglyNumber[index3] * 3, uglyNumber[index5] * 5);
            uglyNumber[curr] = min;
            while(uglyNumber[index2]*2 <= uglyNumber[curr])
                index2++;
            while(uglyNumber[index3]*3 <= uglyNumber[curr])
                index3++;
            while(uglyNumber[index5]*5 <= uglyNumber[curr])
                index5++;
            curr++;
        }
        int ugly = uglyNumber[curr - 1];
        
        return ugly;
    }
    public int getMin(int i, int j, int k) {
        int min = (i < j) ? i : j;
        min = (min < k) ? min : k;
        return min;
    }
}

2. 第一个只出现一次的字符位置

在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符的位置。若为空串,返回-1。位置索引从0开始

这道题的最初思路是对于从头开始的每个字符,遍历后面的字符看是否出现次数为1.但是这种思路的算法复杂度为 O(n) ,这当然是不可接受的啦。

接下来,考虑利用hashmap搞定这个问题,先遍历一次所有字符,出现的字符为 key ,出现的次数为 value 。在第二次遍历的时候,每个查一下 hashmap 中的出现次数是否为1,是就返回。代码如下:

import java.util.*;

public class Solution {
    public int FirstNotRepeatingChar(String str) {
        if(str.length() == 0)
            return -1;
        Map<Integer, Integer> charMap = new HashMap<Integer, Integer>();
        int i = 0;
        for(i = 0; i < str.length(); i++) {
            int num = str.charAt(i) - 'a';
            if(charMap.containsKey(num))
                charMap.put(num, charMap.get(num) + 1);
            else
                charMap.put(num, 1);
        }
        for(i = 0; i < str.length(); i++) {
            int num = str.charAt(i) - 'a';
            if(charMap.get(num) == 1)
               	return i;
        }
        return -1;
    }
}

3. 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

题目暴力解法是:对于从头往后的每个数字对在其后的每个数组成员遍历,数到比该数小的就加1,算法复杂度 O(n^2);

接下来考虑快速一点的解法:参考归并排序的思想,将数组拆分成单个数,在进行合并,不同的是合并时的规则不尽相同。这里在合并的时候,两个指针分别指向量数组的尾部,对其进行比较,若前面数组指针指向元素大于后面数组指针指向元素,那么就有逆序对了,个数就是后面指针的序号+1(因为后面数组也是由小到大排好序的),;若前面数组指针指向元素不大于后面数组指针指向元素,那么没有逆序对。下面是代码实现,在eclipse中调试成功,但是在牛客网上的OJ里怎么也调不通,好方。。。

import java.util.*;
public class Solution {
    public static int count = 0;
    public int InversePairs(int [] array) {
        if(array.length == 1)
            return 0;
        int length = array.length;
        
        int[] firstHalf = new int[length / 2];
        System.arraycopy(array, 0, firstHalf, 0, firstHalf.length);
        InversePairs(firstHalf);
        
        int[] secondHalf = new int[length - length / 2];
        System.arraycopy(array, length / 2, secondHalf, 0, secondHalf.length);
        InversePairs(secondHalf);
        
        int[] tmp = InversePairs(firstHalf, secondHalf);
		System.arraycopy(tmp, 0, array, 0, array.length);
        return count;
    }
    public int[] InversePairs(int [] array1, int [] array2) {
        int[] result = new int[array1.length + array2.length];
        int index1 = array1.length - 1;
        int index2 = array2.length - 1;
      	int index = result.length - 1;
        while(index1 >= 0 && index2 >= 0) {
            if(array1[index1] > array2[index2]){
                count += (index2 + 1);
                result[index] = array1[index1];
                index1--;
            }else{
                result[index] = array2[index2];
                index2--;
            }
            index--;
        }
        while(index1 >= 0) 
            result[index--] = array1[index1--];
        while(index2 >= 0)
            result[index--] = array2[index2--];
        return result;
    }
}

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

输入两个链表,找出它们的第一个公共结点。

注意这个题目的意思:从两个链表的第一个公共结点开始,后面的链表都相同了,根据这个特点,假设两个链表的长度分别为 m,n 。那么第一次遍历的时候得到 m & n ,第二次遍历的时候取 m , n 中较大的那个链表,将其前进 |m-n| 步,剩下的就是简单的同时比较两个链表的指针是否相同了,代码如下:

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
 		ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        int m = 0;
        int n = 0;
        while(p1 != null) {
            m++;
            p1 = p1.next;
        }
        while(p2 != null) {
            n++;
            p2 = p2.next;
        }
        p1 = pHead1;
        p2 = pHead2;
        if(m > n) {
           	for(int i = m - n; i > 0; i--)
                p1 = p1.next;
        }else{
            for(int i = n - m; i > 0; i--)
                p2 = p2.next;
        }
        while(p1 != null && p2 != null) {
            if(p1 == p2)
                return p1;
            p1 = p1.next;
            p2 = p2.next;
        }
        return null;
    }
}

5. 数字在排序数组中出现的次数

统计一个数字在排序数组中出现的次数

考虑到已经是个排好序的数组,那么可以利用二分查找方法,但是对于二分查找来说,只能找到出现的位置,对于多个相同的数,需要对二分查找进行一定的改造来分别找到第一个和最后一个出现的待查找数,代码如下:

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
       	if(array.length == 0 || array == null)
            return 0;
        int start = GetFirstK(array, k, 0, array.length - 1);
        int end = GetLastK(array, k, 0, array.length - 1);
        if(start > -1 && end > -1){
            return end - start + 1;
        }
        return 0;
    }
    public int GetFirstK(int[] array, int k, int start, int end) {
        if(start > end)
            return -1;
        int mid = (start + end) / 2;
        int data = array[mid];
        if(data == k){
            if((mid > 0 && array[mid - 1] != k) || mid == 0)
                return mid;
            end = mid - 1;
        }else if(data > k) {
            end = mid - 1;
        }else{
            start = mid + 1;
        }
        return GetFirstK(array, k, start, end);
    }
    public int GetLastK(int[] array, int k, int start, int end) {
        if(start > end)
            return -1;
        int mid = (start + end) / 2;
        int data = array[mid];
        if(data == k) {
            if((mid < array.length - 1 && array[mid + 1] != k) || mid == array.length - 1) {
                return mid;
            }
            start = mid + 1;
        }else if(data > k) {
            end = mid - 1;
        }else {
            start = mid + 1;
        }
        return GetLastK(array, k, start, end);
    }
}

6. 二叉树的深度

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

思路是:遍历树的过程中当子树没有左子树和右子树的时候,计算出这时的深度,与max depth比较,若比max depth大则更新max depth,代码如下:

/*
public class TreeNode {
	int val = 0;
	TreeNode left = null;
	TreeNode right = null;
	public TreeNode(int val) {
        this.val = val;
    }
};*/
public class Solution {
    public int maxDepth = 0;
	public int TreeDepth(TreeNode pRoot)
    {
        if(pRoot == null)
            return 0;
    	findDepth(pRoot, 0);
        return maxDepth;
    }
    public void findDepth(TreeNode node, int depth) {
        if(node == null)
            return;
        depth++;
        if(node.left == null && node.right == null) {
            if(maxDepth < depth)
                maxDepth = depth;
            return;
        }
        findDepth(node.left, depth);
        findDepth(node.right, depth);
    }
}

7. 平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

这个题目是作为上一题的延伸,大家会自然而然的想到调用上面的方法,自顶向下获取左右子树的深度,若左右深度差大于1则return false。但是考虑到效率问题,书中给出方案是后序遍历二叉树,给出的算法中涉及到了C++的按址传递基本数据类型,这在java中是不允许的,那么在java中非要按址传递基本数据类型的时候该怎么办呢?答案是自定义一个类,类中包含想传递的基本数据类型,通过传类达到传址的目的。
public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
		AuxClass aux = new AuxClass();
        aux.depth = 0;
        return IsBalanced(root, aux);
    }
    
    public boolean IsBalanced(TreeNode node,AuxClass aux){  
        if(node==null){  
            aux.depth=0;  
            return true;  
        }  
        AuxClass left=new AuxClass();  
        AuxClass right=new AuxClass();  
        //get leftTreeDepth and rightTreeDepth of a node.If the 'diff' is bigger than 1,return false;true otherwise  
        if(IsBalanced(node.left,left)&&IsBalanced(node.right,right)){  
            int leftDepth=left.depth;  
            int rightDepth=right.depth;  
            int diff=leftDepth-rightDepth;  
            if(diff==1||diff==-1||diff==0){  
                aux.depth=leftDepth>rightDepth?leftDepth+1:rightDepth+1;  
                return true;  
            }  
        }  
        return false;  
    }
    
    class AuxClass{  
        public int depth;  
    }  
}



8. 数组中只出现一次的数字

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

ok,这道题目用到了位操作。具体的解释在书中有详细的说明,但是书中写的是C++操作,在java中,位操作转换为二进制数时,在获取二进制数位数的时候会有些问题。目前我还没有想出解决方法。


9. 和为S的连续正数序列

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

看到本题目最直观的想法就是从1到50,遍历一遍,但是光是想想就觉得时间复杂度过大。嗯,看了书后发现还是用两个指针指向开始和结束,用动态规划的解法来解答。

设头指针small初始化为1,尾指针big初始化为2,sum为从small到big的和.

1. sum < target, big++

2. sum > target, small++

3. sum == target, 序列找到

代码如下:

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
       	ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
      	int small = 1;
        int big = 2;
        
        while(small < (sum + 1) / 2) {
			while(sumOf(small, big) < sum)
                big++;
            while(sumOf(small, big) > sum)
                small++;
            if(sumOf(small, big) == sum) {
                ArrayList<Integer> array = new ArrayList<Integer>();
				for(int i = small; i <= big; i++)
                    array.add(i);
                result.add(array);
            }
			small++;
        }
        
        return result;
    }
    
    public int sumOf(int start, int end) {
        for(int i = start + 1; i <= end; i++)
           	start += i;
        return start;
    }
    
}

10. 和为S的两个数字


输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

对应每个测试案例,输出两个数,小的先输出。

这个题目和上个题目思路都是类似的,具体操作些许不同,但是思路都是用两个指针分别指向头和尾。代码如下:

import java.util.ArrayList;
public class Solution {
	public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        int small = 0;
        int big = array.length - 1;
        ArrayList<Integer> result = new ArrayList<Integer>();
        while(small < array.length) {
            if(array[small] + array[big] > sum)
                big--;
            if(array[small] + array[big] < sum)
                small++;
            if(array[small] + array[big] == sum){
                result.add(array[small]);
                result.add(array[big]);
                break;
            }
            small++;
        }
        return result;
    }
}








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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值