剑指offer1_简单

1.剑指 Offer 03. 数组中重复的数字
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
(1)自己写的,使用map统计词频,大于1的就是重复的

	Map<Integer,Integer> map=new HashMap<>();
        for(int i=0;i<nums.length;i++){
            if(map.containsKey(nums[i])){
                int count=map.get(nums[i]);
                count++;
                map.put(nums[i],count);
            }else{
                map.put(nums[i],1);
            }
        }
        for(Integer i:map.keySet()){
            if(map.get(i)>1){
                return i;
            }
        }
        return -1;

(2)使用哈希表(Set)记录数组的各个数字,当查找到重复数字则直接返回。
(3)原地交换
在一个长度为 n 的数组 nums 里的所有数字都在 0 ~ n-1 的范围内 。 此说明含义:数组元素的 索引 和 值 是 一对多 的关系。
因此,可遍历数组并通过交换操作,使元素的 索引 与 值 一一对应(即 nums[i] = i)。因而,就能通过索引映射对应的值,起到与字典等价的作用。
遍历中,第一次遇到数字 xx 时,将其交换至索引 xx 处;而当第二次遇到数字 xx 时,一定有 nums[x] = xnums[x]=x ,此时即可得到一组重复数字。

    public int findRepeatNumber(int[] nums) {
        for(int i=0;i<nums.length;i++){
            if(i!=nums[i]){
                if(nums[i]==nums[nums[i]]){
                    return nums[i];
                }
                int temp=nums[i];
                nums[i]=nums[temp];
                nums[temp]=temp;
            }
        }
        return -1;
    }

2.定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
(1)利用了小根堆

class MinStack {

    /** initialize your data structure here. */
    Stack<Integer> stack;
    PriorityQueue<Integer> priorityQueue;
    public MinStack() {
        this.stack=new Stack<>();
        this.priorityQueue=new PriorityQueue<Integer>();
    }
    // public static class MyComparator implements Comparator<Integer>{//自定义比较器有测试用例不通过
    //     @Override
    //     public int compare(Integer o1,Integer o2){
    //         return o1-o2;
    //     }
    // }
    public void push(int x) {
        this.stack.push(x);
        this.priorityQueue.add(x);
    }
    
    public void pop() {
        this.priorityQueue.remove(this.stack.pop());
    }
    
    public int top() {
        return this.stack.peek();
    }
    
    public int min() {
        return priorityQueue.peek();
    }
}

(2)单调栈1:与数据栈不同步pop,min栈没有重复元素

class MinStack {
    Stack<Integer> data;
    Stack<Integer> min;
    /** initialize your data structure here. */
    public MinStack() {
        this.data=new Stack<>();
        this.min=new Stack<>();
    }
    public void push(int x) {
        this.data.push(x);
        if(this.min.isEmpty()||x<=this.min.peek()){
            this.min.push(x);
        }
    }
    
    public void pop() {
        int a=this.data.pop();
        if(a==min.peek()){
            this.min.pop();
        }
    }
    
    public int top() {
        return this.data.peek();
    }
    
    public int min() {
        return this.min.peek();
    }
}

(3)单调栈2:与数据栈同步pop,min栈会有重复元素

class MinStack {
    Stack<Integer> data;
    Stack<Integer> min;
    /** initialize your data structure here. */
    public MinStack() {
        this.data=new Stack<>();
        this.min=new Stack<>();
    }
    
    public void push(int x) {
        if(this.data.isEmpty()){
            this.min.push(x);
        }else{
            int a=this.min.peek();
            if(x<=a){
                this.min.push(x);
            }else{
                this.min.push(a);
            }
        }
        this.data.push(x);
    }
    
    public void pop() {
        this.data.pop();
        this.min.pop();
    }
    
    public int top() {
        return this.data.peek();
    }
    
    public int min() {
        return this.min.peek();
    }
}

3.从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/
9 20
/
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]

	public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res=new ArrayList<>();
        Queue<TreeNode> queue=new LinkedList<>();
        if(root!=null){
            queue.add(root);
        }
        while(!queue.isEmpty()){
            List<Integer> list=new ArrayList<>();
            for(int i=queue.size();i>0;i--){     //将一层的全部出队列
                TreeNode zhong=queue.remove();
                list.add(zhong.val);
                if(zhong.left!=null){
                    queue.add(zhong.left);
                }
                if(zhong.right!=null){
                    queue.add(zhong.right);
                }
            }
            res.add(list);
        }
        return res;
    }

4.数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
(1)map统计

public int majorityElement(int[] nums) {
	Map<Integer,Integer> map=new HashMap();
        for(int i=0;i<nums.length;i++){
            if(!map.containsKey(nums[i])){
                map.put(nums[i],1);
            }else{
                int num=map.get(nums[i]);
                num++;
                map.put(nums[i],num);
            }
        }
        int bound=nums.length/2;
        for(Integer key:map.keySet()){
            if(map.get(key)>bound){
                return key;
            }
        }
        return -1;
}

(2)摩尔投票法:不同的两两抵消

 public int majorityElement(int[] nums) {
		int x=0;
        int vote=0;
        for(int n:nums){
            if(vote==0){
                x=n;
            }
            if(n==x){
                vote++;
            }else{
                vote--;
            }
        }
        return x;
}

5.统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: 0

(1)从前往后统计

	if(nums==null||nums.length==0){
            return 0;
        }
        int count=0;
        for(int i=0;i<nums.length;i++){
            if(nums[i]==target){
                count++;
            }
        }
        return count;

(2)二分法:求target的右边界和target-1的右边界

	public int search(int[] nums, int target) {
        if(nums==null||nums.length==0){
            return 0;
        }
        if(nums.length==1&&nums[0]==target){//防止[1] 1的情况
            return 1;
        }
        return helper(nums,target)-helper(nums,target-1);
        
        
    }
    public int helper(int[] nums,int target){
        int l=0;
        int r=nums.length-1;
        while(l<=r){//注意=号
            int mid=l+(r-l)/2;
            if(nums[mid]<=target){//因为最后要返回l,所以是<=
                l=mid+1;
            }else{
                r=mid-1;
            }
        }
        return l;
    }

(1)遍历

 	public int missingNumber(int[] nums) {
        int i=0;
        for(;i<nums.length;i++){
            if(i!=nums[i]){
                return i;
            }
        }
        return i;
    }

(1)二分:根据题意,数组可以按照以下规则划分为两部分。
左子数组:nums[i]=i ;
右子数组:nums[i] !=i
缺失的数字等于 “右子数组的首位元素” 对应的索引;因此考虑使用二分法查找 “右子数组的首位元素”

 	public int missingNumber(int[] nums) {
        int l=0;
        int r=nums.length-1;
        while(l<=r){
            int mid=l+(r-l)/2;
            if(nums[mid]==mid){
                l=mid+1;
            }else{
                r=mid-1;
            }
        }
        return l;
    }

7.给定一棵二叉搜索树,请找出其中第k大的节点。
示例 1:
输入: root = [3,1,4,null,2], k = 1
3
/
1 4

2
输出: 4
示例 2:
输入: root = [5,3,6,2,4,null,null,1], k = 3
5
/
3 6
/
2 4
/
1
输出: 4
(1)遍历并排序二叉树

 	public int kthLargest(TreeNode root, int k) {
        List<Integer> list=new ArrayList<>();
        pre(root,list);
        Collections.sort(list);
        Collections.reverse(list);
        return list.get(k-1);
    }
    public void pre(TreeNode root,List<Integer> list){
        if(root==null){
            return ;
        }
        list.add(root.val);
        pre(root.left,list);
        pre(root.right,list);
    }

(2)搜索二叉树中序遍历的倒序即为递减序列

class Solution {
    int k;
    int res;
    public int kthLargest(TreeNode root, int k) {
        this.k=k;
        dfs(root);
        return res;
    }
    public void dfs(TreeNode root){
        if(root==null){
            return ;
        }
        dfs(root.right); //注意倒序是先root.right(先去右子树)
        this.k--;
        if(k==0){
            res=root.val;
            return ;
        }
        dfs(root.left);
    }
}

8.剑指 Offer 57 - II. 和为s的连续正数序列
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
示例 1:
输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:
输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
(1)暴力解法:枚举每个正整数为起点,判断以它为起点的序列和sum 是否等于target 即可,由于题目要求序列长度至少大于 2,所以枚举的上界为l<=(target-1)/2

    public int[][] findContinuousSequence(int target) {
        List<int []> res=new ArrayList<int []>();
        int sum=0;
        for(int l=1;l<=(target-1)/2;l++){
            for(int r=l;;r++){
                sum+=r;
                if(sum>target){
                    sum=0;
                    break;
                }else if(sum==target){
                    int[] temp=new int[r-l+1];
                    for(int tem=l;tem<=r;tem++){
                        temp[tem-l]=tem;
                    }
                    res.add(temp);
                    sum=0;
                    break;
                }
            }
        }
        return res.toArray(new int[res.size()][]);
    }

(2)滑动指针:我们用两个指针 l和 r表示当前枚举到的以 l为起点到 r 的区间,sum 表示 [l,r]的区间和

        public int[][] findContinuousSequence(int target) {
        List<int []> res=new ArrayList<int []>();
        int l=1;
        int r=1;
        int sum=0;
        while(l<=(target-1)/2)
        {
            if(sum>target){
                sum-=l;
                l++;
            }else if(sum<target){
                sum+=r;
                r++;
            }else if(sum==target){
                int[] temp=new int[r-l];
                for(int tem=l;tem<r;tem++){
                    temp[tem-l]=tem;
                }
                res.add(temp);
                sum-=l;
                l++;
            }
        }
        return res.toArray(new int[res.size()][]);
    }

9.输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。

示例 1:

输入: “the sky is blue”
输出: “blue is sky the”
示例 2:

输入: " hello world! "
输出: “world! hello”
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:

输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

(1)分割 + 倒序:利用 “字符串分割”、“列表倒序” 的内置函数 (面试时不建议使用) ,可简便地实现本题的字符串翻转要求。

 public String reverseWords(String s) {
        String [] strs=s.trim().split(" ");
        StringBuilder res=new StringBuilder();
        for(int i=strs.length-1;i>-1;i--){
            if(strs[i].equals("")){//注意是"",不是" "
                continue;
            }
            res.append(strs[i]);
            res.append(" ");
        }
        return res.toString().trim();
    }

(2)方法一:双指针
倒序遍历字符串 ss ,记录单词左右索引边界 i , j ;
每确定一个单词的边界,则将其添加至单词列表 res ;
最终,将单词列表拼接为字符串,并返回即可。

	public String reverseWords(String s) {
        s=s.trim();
        int i=s.length()-1;
        int j=i;
        StringBuilder res=new StringBuilder();
        while(i>=0){
            while(i>=0&&s.charAt(i)!=' ') i--;
            res.append(s.substring(i+1,j+1)+" ");  //注意是substring不是subString
            while(i>=0&&s.charAt(i)==' ') i--;
            j=i;
        }
        return res.toString().trim();
    }

10,从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
示例 1:
输入: [1,2,3,4,5]
输出: True
示例 2:
输入: [0,0,1,2,5]
输出: True
此 55 张牌是顺子的 充分条件 如下:
除大小王外,所有牌 无重复 ;
设此 55 张牌中最大的牌为 max ,最小的牌为 min (大小王除外),则需满足:
max - min < 5

(1)集合 Set + 遍历
遍历五张牌,遇到大小王(即 0 )直接跳过。
判别重复: 利用 Set 实现遍历判重, Set 的查找方法的时间复杂度为 O(1) ;
获取最大 / 最小的牌: 借助辅助变量 ma 和 mi ,遍历统计即可。

    public boolean isStraight(int[] nums) {
        Set<Integer> set=new HashSet<>();
        int max=0;
        int min=14;
        for(int i=0;i<=nums.length-1;i++){
            if(nums[i]==0){
                continue;
            }
            max=Math.max(max,nums[i]);
            min=Math.min(min,nums[i]);
            if(set.contains(nums[i])){
                return false;
            }
            set.add(nums[i]);
        }
        return (max-min)<5;
    }

(2)排序 + 遍历
先对数组执行排序。
判别重复: 排序数组中的相同元素位置相邻,因此可通过遍历数组,判断 nums[i] = nums[i + 1]是否成立来判重。
获取最大 / 最小的牌: 排序后,数组末位元素 nums[4]为最大牌;元素 nums[joker]为最小牌,其中 joker 为大小王的数量。

 	public boolean isStraight(int[] nums) {
        int zero=0;
        Arrays.sort(nums);
        for(int i=0;i<nums.length-1;i++){
            if(nums[i]==0){
                zero++;
            }else if(nums[i+1]<=nums[i]){
                return false;
            }
        }
        return (nums[4]-nums[zero])<5;
    }

11.0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
示例 1:

输入: n = 5, m = 3
输出: 3
示例 2:

输入: n = 10, m = 17
输出: 2
(1)使用list集合去存储

	public int lastRemaining(int n, int m) {
        List<Integer> list=new ArrayList<>();
        for(int i=0;i<n;i++){
            list.add(i);
        }
        int idx=0;
        while(n>1){
            idx=(idx+m-1)%n;
            list.remove(idx);
            n--;
        }
        return list.get(0);
    }

(2)数学方法

    public int lastRemaining(int n, int m) {
        int res=0;
        for(int i=2;i<=n;i++){
            res=(res+m)%i;
        }
        return res;
    }

具体参考:https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/javajie-jue-yue-se-fu-huan-wen-ti-gao-su-ni-wei-sh/

12.写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
示例:
输入: a = 1, b = 1
输出: 2

    public int add(int a, int b) {
        int sum=a;
        while(b!=0){
            sum=a^b;
            b=(a&b)<<1;
            a=sum;
        }
        return sum;
    }

13.二叉搜索树的最近公共祖先
(1)迭代

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while(root!=null){
            if(root.val<p.val&&root.val<q.val){
                root=root.right;
            }else if(root.val>p.val&&root.val>q.val){
                root=root.left;
            }else{
                break;
            }
        }
        return root;
    }

(2)递归

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root.val<p.val&&root.val<q.val){
            return lowestCommonAncestor(root.right,p,q);
        }else if(root.val>p.val&&root.val>q.val){
            return lowestCommonAncestor(root.left,p,q);
        }
        return root;
    }

https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/solution/mian-shi-ti-68-i-er-cha-sou-suo-shu-de-zui-jin-g-7/

14.(相似左神中级5:4)写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

    public int strToInt(String str) {
        str=str.trim();//去掉两边的空格
        if(str==null||str.length()==0){
            return 0;
        }
        char[] chars=str.toCharArray();
        boolean neg=chars[0]=='-'?true:false;//先判断是否是负数
        int binary=Integer.MIN_VALUE/10;//-214748364  边界条件,因为要是小于这个数,*10之后必将超过整数能表示的最小值
        int end=Integer.MIN_VALUE%10; //-7   边界条件,当res==-214748364时,cur当前字符要是<-7  那么合并之后也会越界
        int res=0;  //存放最后的结果
        int cur=0;  //当前的字符
        int j=0;   //字符数组开始的位置
        if(neg||chars[0]=='+'){
            j=1;   //若是负数或者第一个符号为'+'时,开始位置为1
        }
        for(int i=j;i<chars.length;i++){
            if(chars[i]<'0'||chars[i]>'9'){//若当前字符不为数字则停止
                break;
            }
            cur='0'-chars[i];
            if(res<binary||(res==binary&&cur<end)){   //越界条件
                return neg?Integer.MIN_VALUE:Integer.MAX_VALUE;
            }
            res=res*10+cur;
        }
        if(!neg&&(res==Integer.MIN_VALUE)){  //防止输入的字符为"2147483648",负数可以表示,但是正数是越界的
            return Integer.MAX_VALUE;
        }
        return neg?res:-res;
    }

https://leetcode-cn.com/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/solution/mian-shi-ti-67-ba-zi-fu-chuan-zhuan-huan-cheng-z-4/
15.(相似左神中级6:2)输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。(二叉树的递归套路)

class Solution {
    public static class Info{//定义信息的类
        public Node start;
        public Node end;
        public Info(Node start,Node end){
            this.start=start;
            this.end=end;
        }
    }
    public Node treeToDoublyList(Node root) {
        if(root==null){
            return null;
        }
        Info info = dfs(root);//递归的建立双向链表
		info.end.right=info.start;//最终需要把链表的头和尾连到一起
		info.start.left=info.end;
		return info.start;
    }
    public static Info dfs(Node head){
        if(head==null){
            return new Info(null,null);
        }
        Info leftInfo=dfs(head.left);//左子树连成双向链表的头尾信息
        Info rightInfo=dfs(head.right);//右子树连成双向链表的头尾信息
        if(leftInfo.end!=null){
            leftInfo.end.right=head;
        }
        if(rightInfo.start!=null){
            rightInfo.start.left=head;
        }
        head.left=leftInfo.end;
        head.right=rightInfo.start;
        return new Info(leftInfo.start!=null ? leftInfo.start : head,rightInfo.end!=null ? rightInfo.end : head);
    }
    
}

16.(相似左神中级6-6)剑指offer42.连续子数组最大和

	public int maxSubArray(int[] nums) {
        if(nums==null||nums.length==0){
            return 0;
        }
        int sum=Integer.MIN_VALUE;
        int cur=0;
        for(int i=0;i<nums.length;i++){
            cur+=nums[i];
            if(cur>sum){
                sum=cur;
            }
            if(cur<0){
                cur=0;
            }
        }
        return sum;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值