LeetCode习题集 有些题可能直接略过了,整理一下之前刷leetcode
381. O(1) 时间插入、删除和获取随机元素 - 允许重复
设计一个支持在平均 时间复杂度 O(1) 下, 执行以下操作的数据结构。
注意: 允许出现重复元素。
insert(val):向集合中插入元素 val。 remove(val):当 val 存在时,从集合中移除一个 val。 getRandom:从现有集合中随机获取一个元素。每个元素被返回的概率应该与其在集合中的数量呈线性相关。 示例:
// 初始化一个空的集合。
RandomizedCollection collection = new RandomizedCollection();
// 向集合中插入 1 。返回 true 表示集合不包含 1 。
collection.insert(1);
// 向集合中插入另一个 1 。返回 false 表示集合包含 1 。集合现在包含 [1,1] 。
collection.insert(1);
// 向集合中插入 2 ,返回 true 。集合现在包含 [1,1,2] 。
collection.insert(2);
// getRandom 应当有 2/3 的概率返回 1 ,1/3 的概率返回 2 。
collection.getRandom();
// 从集合中删除 1 ,返回 true 。集合现在包含 [1,2] 。
collection.remove(1);
// getRandom 应有相同概率返回 1 和 2 。
collection.getRandom();
//维护一个list存储所有val的值
//维护一个map存储每个val在List的位置
//随机只需要再list随机
//删除操作是将所要删除的数将在list中最后一个位置的数放到所要删除的位置
//在更新一下最有一个数在map中的位置即可
class RandomizedCollection {
private Map<Integer,Set<Integer>> map ;
private List<Integer> list ;
private Random random ;
private int size = 0 ;
public RandomizedCollection() {
map = new HashMap<>() ;
list = new ArrayList<>() ;
random = new Random() ;
}
public boolean insert(int val) {
if(map.containsKey(val)){
Set<Integer> indexes = map.get(val) ;
list.add(size,val) ;
indexes.add(size) ;
size++ ;
return false ;
}else{
Set<Integer> indexes = new HashSet<>() ;
map.put(val,indexes) ;
list.add(size,val) ;
indexes.add(size) ;
size++ ;
return true ;
}
}
public boolean remove(int val) {
if(!map.containsKey(val)){
return false ;
}
Set<Integer> indexes = map.get(val) ;
if(list.get(size-1) == val){
indexes.remove(size-1) ;
size-- ;
}else{
//删除下标
Iterator<Integer> it = indexes.iterator() ;
int index = it.next() ;
it.remove();
//把list的最后一个值放到对应下标那
int last = list.get(size-1) ;
list.set(index,last) ;
Set<Integer> set = map.get(last) ;
//更改我最后一个值的下标
set.remove(size-1) ;
set.add(index) ;
size-- ;
}
if(indexes.size() == 0){
map.remove(val) ;
}
return true ;
}
public int getRandom() {
return list.get(random.nextInt(size));
}
}
/**
* Your RandomizedCollection object will be instantiated and called as such:
* RandomizedCollection obj = new RandomizedCollection();
* boolean param_1 = obj.insert(val);
* boolean param_2 = obj.remove(val);
* int param_3 = obj.getRandom();
*/
382. 链表随机节点
给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。
进阶: 如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现?
示例:
// 初始化一个单链表 [1,2,3]. ListNode head = new ListNode(1); head.next = new ListNode(2); head.next.next = new ListNode(3); Solution solution = new Solution(head);
// getRandom()方法应随机返回1,2,3中的一个,保证每个元素被返回的概率相等。 solution.getRandom();
生成随机数,
如果随机数为0,就记录当前结点的值,一直循环,每次随机数为0,就记录当前结点的值
一直到链表循环结束
就可以做到随机访问的功能
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
private ListNode head;
public Solution(ListNode head) {
this.head = head;
}
public int getRandom() {
int res = head.val;
ListNode no = head.next;
int i = 2;
Random random = new Random();
while(no!=null){
if(random.nextInt(i) == 0){
res = no.val;
}
i++;
no = no.next;
}
return res;
}
}
/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(head);
* int param_1 = obj.getRandom();
*/
383. 赎金信
给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成。如果可以构成,返回 true ;否则返回 false。
(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。)
注意:
你可以假设两个字符串均只含有小写字母。
canConstruct("a", "b") -> false canConstruct("aa", "ab") -> false canConstruct("aa", "aab") -> true
String.indexof(a,b)
从b索引开始找a字符
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
if (ransomNote.length() > magazine.length()) {
return false;
}
int[] chars = new int[26];
for (int i = 0; i < ransomNote.length(); i++) {
char c = ransomNote.charAt(i);
int index = magazine.indexOf(c, chars[c - 'a']);
if (index == -1) {
return false;
}
chars[c - 'a'] = index + 1;
}
return true;
}
}
384. 打乱数组
打乱一个没有重复元素的数组。
示例:
// 以数字集合 1, 2 和 3 初始化数组。 int[] nums = {1,2,3}; Solution solution = new Solution(nums);
// 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。 solution.shuffle();
// 重设数组到它的初始状态[1,2,3]。 solution.reset();
// 随机返回数组[1,2,3]打乱后的结果。 solution.shuffle();
class Solution {
private int[] nums;
private int[] originalNums;
public Solution(int[] nums) {
this.nums = nums;
this.originalNums = Arrays.copyOf(nums, nums.length);
}
/**
* Resets the array to its original configuration and return it.
*/
public int[] reset() {
return this.originalNums;
}
/**
* Returns a random shuffling of the array.
*/
public int[] shuffle() {
Random random = new Random();
for (int i = 0; i < nums.length / 2; i++) {
// 每次只需拿第一个元素进行交换即可
swap(nums, 0, random.nextInt(nums.length));
}
return nums;
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(nums);
* int[] param_1 = obj.reset();
* int[] param_2 = obj.shuffle();
*/
385. 迷你语法分析器
给定一个用字符串表示的整数的嵌套列表,实现一个解析它的语法分析器。
列表中的每个元素只可能是整数或整数嵌套列表
提示:你可以假定这些字符串都是格式良好的:
字符串非空 字符串不包含空格 字符串只包含数字0-9, [, - ,, ]
示例 1:
给定 s = "324",
你应该返回一个 NestedInteger 对象,其中只包含整数值 324。
示例 2:
给定 s = "[123,[456,[789]]]",
返回一个 NestedInteger 对象包含一个有两个元素的嵌套列表:
1. 一个 integer 包含值 123
2. 一个包含两个元素的嵌套列表:
i. 一个 integer 包含值 456
ii. 一个包含一个元素的嵌套列表
a. 一个 integer 包含值 789
扫描数字,
如果不是数字就扫描层数
扫描完层数,就递归调用方法,
只有最里层的时候才能直接打印
/**
* // This is the interface that allows for creating nested lists.
* // You should not implement it, or speculate about its implementation
* public interface NestedInteger {
* // Constructor initializes an empty nested list.
* public NestedInteger();
*
* // Constructor initializes a single integer.
* public NestedInteger(int value);
*
* // @return true if this NestedInteger holds a single integer, rather than a nested list.
* public boolean isInteger();
*
* // @return the single integer that this NestedInteger holds, if it holds a single integer
* // Return null if this NestedInteger holds a nested list
* public Integer getInteger();
*
* // Set this NestedInteger to hold a single integer.
* public void setInteger(int value);
*
* // Set this NestedInteger to hold a nested list and adds a nested integer to it.
* public void add(NestedInteger ni);
*
* // @return the nested list that this NestedInteger holds, if it holds a nested list
* // Return null if this NestedInteger holds a single integer
* public List<NestedInteger> getList();
* }
*/
class Solution {
public NestedInteger deserialize(String s) {
if(s.charAt(0)!='[') {
return new NestedInteger(Integer.valueOf(s));
}
else {
return deserialize1(s.substring(1));
}
}
public NestedInteger deserialize1(String s) {
NestedInteger res = new NestedInteger();
//从左到右扫描
for(int i=0;i<s.length();i++) {
char c = s.charAt(i);
if(c>='0'&&c<='9'||c=='-') {
int n = 0; int flag = 1;
for(;i<s.length();i++) {
c = s.charAt(i);
if(c>='0'&&c<='9') {
n = n*10 + c-'0';
} else if(c=='-'){
flag = -1;
} else {
i = i-1;
break;
}
}
res.add(new NestedInteger(flag*n));
}
else if(c=='[') {
int index = i;
int counter = 0;
for(;i<s.length();i++) {
c = s.charAt(i);
if(c=='[') counter++;
else if(c==']') counter--;
if(counter==0) {
res.add(deserialize1(s.substring(index+1,i)));
break;
}
}
}
}
return res;
}
}
386. 字典序排数
给定一个整数 n, 返回从 1 到 n 的字典顺序。
例如,
给定 n =1 3,返回 [1,10,11,12,13,2,3,4,5,6,7,8,9] 。
请尽可能的优化算法的时间复杂度和空间复杂度。 输入的数据 n 小于等于 5,000,000。
通过次数6,670提交次数9,748
字典序,每次都是从1到9开始排,只有到最后一位的时候递归调用,会直接返回
然后每次都是递归调用下一位,更改下一位的
class Solution {
void reversalTree2(int root, int n, ArrayList<Integer> list) {
list.add(root);
int start = root * 10;
for (int i = start; i <= n && i <= start + 9; i++) {
reversalTree2(i, n, list);
}
}
public List<Integer> lexicalOrder(int n) {
ArrayList<Integer> list = new ArrayList<>(n);
for (int i=1; i <= n && i <= 9; i++) {
reversalTree2(i, n, list);
}
return list;
}
}
387. 字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
案例:
s = "leetcode" 返回 0.
s = "loveleetcode", 返回 2.
注意事项:您可以假定该字符串只包含小写字母。
indexOf找到第一个对应字符的下标
lastIndexOf找到最后一个对应字符的下标
如果都存在,并且相等下标,说明只有一个该字符,
就记录下,匹配是否是最小的,保存最小的
如果n还是等于字符串长度,证明都是重复的字符,返回-1
class Solution {
public int firstUniqChar(String s) {
//fast
int n = s.length();
for(int i = 'a'; i<='z';i++){
int start = s.indexOf(i);
int end = s.lastIndexOf(i);
if(start == end && start != -1){
n = Math.min(start, n);
}
}
if(n==s.length()){
return -1;
}else{
return n;
}
}}
388. 文件的最长绝对路径
假设我们以下述方式将我们的文件系统抽象成一个字符串:
字符串 "dir\n\tsubdir1\n\tsubdir2\n\t\tfile.ext" 表示:
dir
subdir1
subdir2
file.ext
目录 dir 包含一个空的子目录 subdir1 和一个包含一个文件 file.ext 的子目录 subdir2 。
字符串 "dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdir1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext" 表示:
dir
subdir1
file1.ext
subsubdir1
subdir2
subsubdir2
file2.ext
目录 dir 包含两个子目录 subdir1 和 subdir2。 subdir1 包含一个文件 file1.ext 和一个空的二级子目录 subsubdir1。subdir2 包含一个二级子目录 subsubdir2 ,其中包含一个文件 file2.ext。
我们致力于寻找我们文件系统中文件的最长 (按字符的数量统计) 绝对路径。例如,在上述的第二个例子中,最长路径为 "dir/subdir2/subsubdir2/file2.ext",其长度为 32 (不包含双引号)。
给定一个以上述格式表示文件系统的字符串,返回文件系统中文件的最长绝对路径的长度。 如果系统中没有文件,返回 0。
说明:
文件名至少存在一个 . 和一个扩展名。 目录或者子目录的名字不能包含 .。 要求时间复杂度为 O(n) ,其中 n 是输入字符串的大小。
请注意,如果存在路径 aaaaaaaaaaaaaaaaaaaaa/sth.png 的话,那么 a/aa/aaa/file1.txt 就不是一个最长的路径。
“\t是一个字符”
其他的直接暴力搜索就行
合理利用正则表达式可以提高效率
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class Solution {
//bx
public int lengthLongestPath(String input) {
if (input.length() == 0) {
return 0;
}
int res = 0;
String[] dirs = input.split("\n");
int[] sum = new int[dirs.length+1];
//StringBuilder sb = new StringBuilder();
for (String s : dirs) {
int level = s.lastIndexOf('\t') + 2;
//if (level == 2){
//sb.setLength(sum[1]);
//}
//sb.append(s.substring(s.lastIndexOf("\t")+1));
int len = s.length() - (level - 1);
if (s.contains(".")) {
res = Math.max(res, sum[level - 1] + len);
} else {
sum[level] = sum[level - 1] + len + 1; //是目录,要+1,目录有个/的
//sb.append("\\");
}
}
//System.out.println(sb.toString());
return res;
}
}
389. 找不同
给定两个字符串 s 和 t,它们只包含小写字母。
字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。
请找出在 t 中被添加的字母。
示例:
输入: s = "abcd" t = "abcde"
输出: e
解释: 'e' 是那个被添加的字母。
class Solution {
// public char findTheDifference(String s, String t) {
// char res = t.charAt(t.length()-1);
// for(int i=0; i<s.length(); i++){
// res ^= s.charAt(i);
// res ^= t.charAt(i);
// }
// return res;
// }
public char findTheDifference(String s, String t) {
char[] ss = s.toCharArray();
char[] tt = t.toCharArray();
char res = tt[tt.length - 1];
for(int i=0; i<ss.length; i++){
res += tt[i] - ss[i];
}
return res;
}
}
390. 消除游戏
给定一个从1 到 n 排序的整数列表。 首先,从左到右,从第一个数字开始,每隔一个数字进行删除,直到列表的末尾。 第二步,在剩下的数字中,从右到左,从倒数第一个数字开始,每隔一个数字进行删除,直到列表开头。 我们不断重复这两步,从左到右和从右到左交替进行,直到只剩下一个数字。 返回长度为 n 的列表中,最后剩下的数字。
示例:
输入: n = 9, 1 2 3 4 5 6 7 8 9 2 4 6 8 2 6 6
输出: 6
isNormal是判断是单数行还是双数行
单数行就每隔一个数字找一个
双数也是每隔一个找一个,但是是后面找的,其实后面找和正面找一样的,最后找的是最后一个数字
倒着找的时候,如果是2的倍数,就从开头开始,如果不是2的倍数就默认走一步
因为倒着走过来,不是二的倍数,走不到最开始的地方
每次都数组长度除2
步数乘2(相当于上面已经删掉了一部分)
最下面那一行是真正大佬的思路
class Solution {
public int lastRemaining(int n) {
boolean isNormal = true;
int len = n;
int start = 1;
int step = 1;
while (len > 1) {
if (isNormal) {
start = start + step;
} else {
start = len % 2 == 0 ? start : start + step;
}
step = step * 2;
len = len / 2;
isNormal = !isNormal;
}
return start;
// return n == 1 ? 1 : 2 * (n / 2 + 1 - lastRemaining(n / 2));
}
}