目录
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
利用数据结构特点,容易想到使用哈希表(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) 线性查找
本题给出的二维数组中没一行都是排过序的,也就是说我们可以把它从右上角看成一个树,往左变小,往右变大。(往左变小,往下变小)
那我们直到了这个特性,就可以用树的方法求解了
从右上角开始和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 倍,这样可保证字符数组可以容纳所有替换后的字符。
- List item
- 获得 s 的长度 length
- 创建字符数组 array,其长度为 length * 3
- 初始化 size 为 0,size 表示替换后的字符串的长度
- 从左到右遍历字符串 s
- 获得 s 的当前字符 c
- 如果字符 c 是空格,则令 array[size] = ‘%’,array[size + 1] = ‘2’
array[size + 2] = ‘0’,并将 size 的值加 3 - 如果字符 c 不是空格,则令 array[size] = c,并将 size 的值加 1
- 遍历结束之后,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++;
}
}
附加练习:链表
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++;
}
}
}
}
}