LeetCode刷题记录

1.数组中重复的数字(本题考验沟通)

首先问清楚需求是时间还是空间,然后选择方法。

找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

示例 1:
输入: [2, 3, 1, 0, 2, 5, 3]
输出:2 或 3

(1)原地置换法

参考:Leetcode题解
大概就是一个萝卜一个坑的原理。
例如

value: 2 1 3 5 0 4
index: 0 1 2 3 4 5

值和对应index的值
如果不同就要找到对应的
比如 index:0 的位置是2,那么这不是它要的数字,它就和index:2交换值
交换后如下:

value: 3 1 2 5 0 4
index: 0 1 2 3 4 5

此时仍然不是对应的值,于是和index:3 互换值

value: 5 1 2 3 0 4
index: 0 1 2 3 4 5

仍然不是对应的值,于是和index:5 互换值

value: 4 1 2 3 0 5
index: 0 1 2 3 4 5

仍然不是对应的值,于是和index:5 互换值

value: 0 1 2 3 4 5
index: 0 1 2 3 4 5

此时全部完成
如果互换时出现值相等就代表找到了重复值。
个人认为这种方法只是在本题中适用,如果值大于index那么就会无法执行。
并且数组越长,处理越复杂。
不过空间节省效果非常好。

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

                temp=nums[nums[i]];
                nums[nums[i]]=nums[i];
                nums[i]=temp;

            }
        }
        return 0;
    }
}

(2)哈希表 / Set

哈希表(HashSet):详解
参考文章:Leetcode

利用数据结构特点,容易想到使用哈希表(Set)记录数组的各个数字,当查找到重复数字则直接返回。

算法流程:
初始化: 新建 HashSet ,记为 dicdic ;
遍历数组 numsnums 中的每个数字 numnum :
当 numnum 在 dicdic 中,说明重复,直接返回 numnum ;
将 numnum 添加至 dicdic 中;
返回 -1−1 。本题中一定有重复数字,因此这里返回多少都可以。

复杂度分析:
时间复杂度 O(N) : 遍历数组使用 O(N) ,HashSet 添加与查找元素皆为 O(1) 。
空间复杂度O(N) : HashSet 占用 O(N) 大小的额外空间。

class Solution {
    public int findRepeatNumber(int[] nums) {
        Set<Integer> dic =new HashSet<>();
        for(int num:nums){
            if(dic.contains(num)){
                return num;
            }
            else{
                 dic.add(num);
            }
        }
        return 0;
    }
}

2.二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

示例:

[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。

(1)暴力法(双for)

(2) 线性查找

本题给出的二维数组中没一行都是排过序的,也就是说我们可以把它从右上角看成一个树,往左变小,往右变大。(往左变小,往下变小)
LeetCode-Krahets大佬的图
那我们直到了这个特性,就可以用树的方法求解了
从右上角开始和target比对,如果比target大,就找它的左节点,target比它小,就找左节点。下一级同理。

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
    //先判断数组合法
         if(matrix == null || matrix.length == 0 || matrix[0].length == 0){ 
             return false;
         }


        int rows = matrix.length;
        int cols = matrix[0].length;
        int row = 0;
        int col=cols-1;



//超出范围说明无target
        while(row < rows && col >= 0){
          if(matrix[row][col]==target){
              return true;
          }
          else if(matrix[row][col]>target && col>=0){
            col--;
          }
          else if(matrix[row][col]<target && row<=rows-1){
              row++;
          }

        }
        return false;
    }
}

3.替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1:

输入:s = “We are happy.” 输出:“We%20are%20happy.”

(1)字符数组

由于每次替换从 1 个字符变成 3 个字符,使用字符数组可方便地进行替换。建立字符数组地长度为 s 的长度的 3 倍,这样可保证字符数组可以容纳所有替换后的字符。

  1. List item
  2. 获得 s 的长度 length
  3. 创建字符数组 array,其长度为 length * 3
  4. 初始化 size 为 0,size 表示替换后的字符串的长度
  5. 从左到右遍历字符串 s
  6. 获得 s 的当前字符 c
  7. 如果字符 c 是空格,则令 array[size] = ‘%’,array[size + 1] = ‘2’
    array[size + 2] = ‘0’,并将 size 的值加 3
  8. 如果字符 c 不是空格,则令 array[size] = c,并将 size 的值加 1
  9. 遍历结束之后,size 的值等于替换后的字符串的长度,从 array 的前 size 个字符创建新字符串,并返回新字符串

复杂性分析

时间复杂度:O(n)O(n)。遍历字符串 s 一遍。
空间复杂度:O(n)O(n)。额外创建字符数组,长度为 s 的长度的 3 倍。

//如果不为空格存入数组,如果为空格处理后存入数组
class Solution {
    public String replaceSpace(String s) {
        int length = s.length();
        char[] array = new char[length * 3];//三倍长度预留充分
        int size = 0;	//索引->array
        for (int i = 0; i < length; i++) {
            char c = s.charAt(i); //通过索引搜索字符
            if (c == ' ') {
                array[size++] = '%';
                array[size++] = '2';
                array[size++] = '0';
            } else {
                array[size++] = c;
            }
        }
        String newStr = new String(array, 0, size);//数组转字符串
        return newStr;


    }
}

(2)Java自带方法

class Solution {
    public String replaceSpace(String s) {
        String str = s.replace(" ","%20");
        return str;
    }
}

4.从尾到头打印链表

(1)递归法

思路
重复递归直到最后一个节点,开始返回值,然后存入数组,直接返回。
采用动态初始化数组可以提高时间效率。
代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    int[] a;
    int i=0;
    int j=0;
    public int[] reversePrint(ListNode head) {
        reback(head);
        return a;
    }
    void reback(ListNode node){
        if(node==null){
            a = new int[i];
            return;
        }
        i++;
        reback(node.next);
        a[j]=node.val;
        j++;

    }
}

附加练习:链表

LeetCode链表专题

5.重建二叉树

(1) 递归(分而治之)

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:

    3   
   / \  
 9    20
     /  \    
    15   7

读题阶段:
本题千万不要被返回误导,没那么复杂,只用返回一个树就OK.
而且构造LeetCode已经给了。

题目分析
本题主要是要理解为啥给出前序遍历,中序遍历,他们有啥不同,他们怎么确定一个树。

主要的思路还是使用递归,分而治之。这样可以提高时间效率,并且容易实现。

前序遍历主要顺序是 中左右
中序遍历主要顺序是 左中右
所以我们可以根据前序遍历确定节点的值,通过中序遍历得知左节点的值在 i-1的位置(i=中节点索引),右节点在i+1的位置。

外部链接
由于我的表达可能只有自己看的懂,所以借用大佬的一些解题思路。
请移步:LeetCode

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {    //分而治之
    int[] preorder; //记录传递前序
    HashMap<Integer, Integer> dic = new HashMap<>();//创建字典
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        this.preorder = preorder;   //记录前序遍历结果
        for(int i=0;i<inorder.length;i++){  //遍历中序遍历结果
            dic.put(inorder[i],i);  //将遍历结果和序号一起存入字典。
        }
       return rescur(0,0,inorder.length-1);//开始递归


    }
    public TreeNode rescur(int root,int left,int right){       //root,left,right 都是索引,
        if(left > right) return null;   //如果left>right说明节点不存在
        TreeNode node = new TreeNode(preorder[root]);   //创建节点
        int i = dic.get(preorder[root]);//获取该节点所在中序遍历中的位置
        /*注:前序遍历顺序(中左右) 中序遍历顺序(左中右)*/
        node.left=rescur(root+1,left,i-1);//左节点=(前序遍历中的下一个的index,左节点长度,中序遍历的前一个)
        node.right=rescur(i-left+root+1,i+1,right);//右节点=(根节点索引-左子节点长度+1,中序节点的后一个,右节点长度)
        return node;
    }
}

6.美团题–小美的用户名

小美是美团的前端工程师,为了防止系统被恶意攻击,小美必须要在用户输入用户名之前做一个合法性检查,一个合法的用户名必须满足以下几个要求:

用户名的首字符必须是大写或者小写字母。 用户名只能包含大小写字母,数字。 用户名需要包含至少一个字母和一个数字。 如果用户名合法,请输出
“Accept”,反之输出 “Wrong”。 格式:

输入:

  • 输入第一行包含一个正整数 T,表示需要检验的用户名数量。
  • 接下来有 T 行,每行一个字符串 s,表示输入的用户名。 输出:
  • 对于每一个输入的用户名 s,请输出一行,即按题目要求输出一个字符串。

示例:

输入:
5
Ooook
Hhhh666
ABCD
Meituan
6666 输出:
Wrong
Accept
Wrong
Wrong
Wrong

(1)正则表达式判断

package MeiTuan;

import java.util.Scanner;
import java.util.regex.Pattern;

public class UserNameCheck {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		for(int i=0;i<n;i++) {
			String username = sc.next();
			
			System.out.println(check(username));
		}
	}
	
	public static String check(String name) {
		int cNum=0;
		int cEn=0;
		String fristchart =String.valueOf(name.charAt(0));
		if (fristchart.matches("[A-Za-z]+$")) {
			if (name.matches("[A-Za-z0-9]+$")) {
				for(int i=0;i<name.length();i++) {
					String teString = String.valueOf(name.charAt(i));
					if(teString.matches("[0-9]+$")) {
						cNum++;
					}
					if (teString.matches("[A-Za-z]+$")) {
						cEn++;
					}
				}
			}
			else {
				return "Wrong";
			}
		}
		else {
			return "Wrong";
		}
		
		if (cNum>=1 && cEn >=1) {
			return "Accept";
		}
		else {
			return "Wrong";
		}
	}

}

7.美团题:小美的仓库整理

小美是美团仓库的管理员,她会根据单据的要求按顺序取出仓库中的货物,每取出一件货物后会把剩余货物重新堆放,使得自己方便查找。已知货物入库的时候是按顺序堆放在一起的。如果小美取出其中一件货物,则会把货物所在的一堆物品以取出的货物为界分成两堆,这样可以保证货物局部的顺序不变。
已知货物最初是按 1~n 的顺序堆放的,每件货物的重量为 w[i]
,小美会根据单据依次不放回的取出货物。请问根据上述操作,小美每取出一件货物之后,重量和最大的一堆货物重量是多少?

输入:

  • 输入第一行包含一个正整数 n ,表示货物的数量。
  • 输入第二行包含 n 个正整数,表示 1~n 号货物的重量 w[i] 。
  • 输入第三行有 n 个数,表示小美按顺序取出的货物的编号,也就是一个 1~n 的全排列。 输出:
  • 输出包含 n 行,每行一个整数,表示每取出一件货物以后,对于重量和最大的一堆货物,其重量和为多少。

示例:

输入:
5
3 2 4 4 5
4 3 5 2 1 输出:
9
5
5
3
0 解释: 原本的状态是 {{3,2,4,4,5}} ,取出 4 号货物后,得到 {{3,2,4},{5}} ,第一堆货物的和是 9 ,然后取出 3 号货物得到 {{3,2}{5}} ,此时第一堆和第二堆的和都是 5 ,以此类推。

(1)解答

package MeiTuan;

import java.util.Scanner;

public class Tws {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in); //实例化获取输入的类
		int n =sc.nextInt();	//获取第一行n
		int[] a = new int[n];	//初始化数组a
		
		for(int i=0;i<n;i++) {	//获取第二行输入存入数组a
			a[i] = sc.nextInt();
			
		}

		for (int i = 0; i<n; i++) {	
			int index = sc.nextInt();	//获取第三行
			int counta=0;	//计数:取出值后面的值
			int countb=0;	//计数:取出值前面的值
			a[index-1]=0;	//将值取出=重量设0
			
			for(int j=0;j<index;j++) {	//计算index前的和
				countb = countb + a[j];
			}
			for(int j=index;j<a.length;j++) {//计算index后的和
				counta = counta + a[j];
			}
			
			if(counta > countb)	//返回最大的结果
			System.out.println(counta);
			else
			System.out.println(countb);
		}
	}

}

8.小美的跑腿代购

小美的一个兼职是美团的一名跑腿代购员,她有 n 个订单可以接,订单编号是 1~n ,但是因为订单的时效性,他只能选择其中 m 个订单接取,精明的小美当然希望自己总的获利是最大的,已知,一份订单会提供以下信息,跑腿价格 v ,商品重量 w kg,商品每重 1kg ,代购费用要加 2 元,而一份订单可以赚到的钱是跑腿价格和重量加价之和。小美可是开兰博基尼送货的人,所以自然不会在意自己会累这种事情。请问小美应该选择哪些订单,使得自己获得的钱最多。
请你按照选择的订单编号的从小到大顺序,如果存在多种方案,输出订单编号字典序较小的方案。

格式:

输入:

  • 输入第一行包含两个正整数 n,m,表示订单的数量和小美可以接的订单数量。
  • 接下来有 n 行,第 i 行表示 i-1 号订单的信息。每行有两个正整数 v 和 w ,表示一个订单的跑腿价格和商品重量。 输出:
  • 输出包含 m 个 1~n 之间的正整数,中间用空格隔开,表示选择的订单编号。

示例:

输入:
5 2
5 10
8 9
1 4
7 9
6 10
输出:2 5

(1)暴力解法

先把数组排序,倒过来找到最大的且满足要求的订单金额,然后遍历原数组找到对应的订单好
这个解法太蠢了,时间空间占用双高,但是我暂时也不会别的,等后期再改了。

package MeiTuan;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Scanner;

public class No3 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int m = sc.nextInt();
		int[] a = new int[n];
		int[] b = new int[n];
		for(int i=0;i<n;i++) {
			int v = sc.nextInt();
			int wv = sc.nextInt()*2;
			a[i] = v+wv;
			b[i] = v+wv;
		}
		
		Arrays.sort(a);
		
		for(int j=a.length-1;j>a.length-1-m;j--) {
			int value = a[j];
			for(int i=0;i<a.length;i++) {
				if(b[i]==value) {
					System.out.print(i+1+" ");
					b[i]=0;
				}
			}
		}
		
	}

}

9.小团的复制粘贴

小团是一个莫得感情的 CtrlCV 大师,他有一个下标从 1 开始的序列 A 和一个初始全部为 -1 序列 B ,两个序列的长度都是 n
。他会进行若干次操作,每一次操作,他都会选择 A 序列中一段连续区间,将其粘贴到 B 序列中的某一个连续的位置,在这个过程中他也会查询 B
序列中某一个位置上的值。

我们用如下的方式表示他的粘贴操作和查询操作:

粘贴操作:1 k x y,表示把 A 序列中从下标 x 位置开始的连续 k 个元素粘贴到 B 序列中从下标 y 开始的连续 k个位置上。原始序列中的元素被覆盖。(数据保证不会出现粘贴后 k 个元素超出 B 序列原有长度的情况)

查询操作:2 x,表示询问B序列下标x 处的值是多少。

格式:
输入:

  • 输入第一行包含一个正整数 n ,表示序列 A 和序列 B 的长度。
  • 输入第二行包含 n 个正整数,表示序列 A 中的 n 个元素,第 i 个数字表示下标为 i 的位置上的元素,每一个元素保证在 10^9 以内。
  • 输入第三行是一个操作数 m ,表示进行的操作数量。
  • 接下来 m 行,每行第一个数字为 1 或 2 ,具体操作细节详见题面。
    输出:
  • 对于每一个操作 2 输出一行,每行仅包含一个正整数,表示针对某一个询问的答案。

示例 1:

输入:
5
1 2 3 4 5
5
2 1
2 5
1 2 3 4
2 3
2 5
输出:
-1
-1
-1
4

(1) 解答题

package MeiTuan;

import java.util.Scanner;

public class No4 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int[] a = new int[n];
		int[] b = new int[n];
		
		for(int i=0;i<n;i++) {
			a[i]=sc.nextInt();
			b[i]=-1;
		}
		
		int m =sc.nextInt();
		for(int i=0;i<m;i++) {
			n=sc.nextInt();
			if(n==2) {
				n=sc.nextInt();
				System.out.println(b[n-1]);
			}
			else {
				int k = sc.nextInt();
				int x = sc.nextInt()-1;
				int y = sc.nextInt()-1;
				for(int j=0;j<k;j++) {
					b[y]=a[x];
					y++;
					x++;
				}
			}
		}
	}

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值