文章目录
一、数组
1.删除排序数组中的重复项
我使用了双指针
class Solution {
public int removeDuplicates(int[] nums) {
if(nums.length==1) return 1;
int sum=1;
int i=0;//slow
int j=1;//fast
while(j!=nums.length){
if(nums[i]==nums[j]){j++;}
else{
nums[i+1]=nums[j];i++;sum++;
}
}
return sum;
}
}
感觉不太行
别人的双指针:
//双指针解决
public int removeDuplicates(int[] A) {
//边界条件判断
if (A == null || A.length == 0)
return 0;
int left = 0;
for (int right = 1; right < A.length; right++)
//如果左指针和右指针指向的值一样,说明有重复的,
//这个时候,左指针不动,右指针继续往右移。如果他俩
//指向的值不一样就把右指针指向的值往前挪
if (A[left] != A[right])
A[++left] = A[right];
return ++left;
}
作者:数据结构和算法
链接:https://leetcode.cn/leetbook/read/top-interview-questions-easy/x2gy9m/?discussion=4Zkrel
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
分析:
2.旋转数组
两次翻转
class Solution {
public void rotate(int[] nums, int k) {
int length=nums.length;
k=k%length;
exchange(nums,0,length-1);
exchange(nums,0,k-1);
exchange(nums,k,length-1);
}
void exchange(int[]nums,int start,int end)
{
while(start<end){
int temp = nums[start];
nums[start++] = nums[end];
nums[end--]=temp;
}
}
}
还可以用环形旋转
3.存在重复元素
回忆一下Set用法,算法直接粘贴过来:
public boolean containsDuplicate(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int num : nums) {
//因为集合set中不能有重复的元素,如果有重复的
//元素添加,就会添加失败
if (!set.add(num))
return true;
}
return false;
}
set集合中的元素是不能有重复的,在添加的时候(add方法)如果有重复的,会把之前的值给覆盖,并且返回false
4.两数之和
HashMap解法
Java HashMap
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射
HashMap 是无序的,即不会记录插入的顺序
创建HashMap对象:
HashMap<Integer, String> Sites = new HashMap<Integer, String>();
常用的HashMap方法
put() 将键/值对添加到 hashMap 中
remove() 删除 hashMap 中指定键 key 的映射关系
get() 获取指定 key 对应对 value
values() 返回 hashMap 中存在的所有 value 值
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
思路:将数组中value值作为key,index值作为value,设计一个循环把数组中每一个值都放进HashMap中,然后在放入的同时检查HashMap中是否存在一个数和当前的数的和为target
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap <Integer,Integer>map=new HashMap<>();
for(int i=0;i<nums.length;i++){
if(map.get(target-nums[i])!=null)
return new int[]{map.get(target-nums[i]),i};
else map.put(nums[i],i);
}
return new int[]{0,0};
}
}
5.三数之和
题目:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。注意,输出的顺序和三元组的顺序并不重要。
continue 用来结束当前循环,并进入下一次循环,即仅仅这一次循环结束了
Class Arrays
该类包含用于操作数组的各种方法(例如排序和搜索)。 此类还包含一个静态工厂,允许将数组视为列表。
变量和类型 | 方法 | 描述 |
---|---|---|
static List | asList(T… a) | 返回由指定数组支持的固定大小的列表。 |
思路:由后面两个注意可以推测不用从“二数之和”的方法出发推三数之和。本题用双指针的方法,定住一个,双指针寻找两个相加为定点负数的数。因为顺序随意,所以可以进行排序来优化算法,两指针从两头往中间靠拢,涉及到的判断条件有:①排除重复的(分为定点数重复以及双指针指到的数重复) ②和大于定点的负数说明大了,右指针左移,另外一边同理
构造函数: List<List> list=new ArrayList<>();
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> list=new ArrayList<>();
for(int i=0;i<nums.length;i++){
if(i>0&&nums[i]==nums[i-1]) continue;
if(nums[i]>0) break;
int target=-nums[i];
int left=i+1;
int right=nums.length-1;
while(left<right){
if(nums[left] + nums[right] == target) {list.add(Arrays.asList(nums[i],nums[left],nums[right]));
while(left<right && nums[left]==nums[left+1]) left++;
while(left<right && nums[right]==nums[right-1]) right--;
left++;
right--;}
else if((nums[left] + nums[right])<target) left++;
else right--;
}
}
return list;
}
}
二、字符串
1.字符串中的第一个唯一字符
熟悉哈希表
给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。
⭐哈希表中添加的方法是 put(),ArrayList是 add()
将字符串转换为char数组的方法:toCharArray()
⭐getOrDefault()
getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。
getOrDefault() 方法的语法为:
hashmap.getOrDefault(Object key, V defaultValue)
class Solution {
public int firstUniqChar(String s) {
HashMap<Character,Integer>map = new HashMap<>();
char[]chars=s.toCharArray();
for(char c:chars){
map.put(c,map.getOrDefault(c,0)+1);
}
int count=0;
for(char c:chars){
if(map.get(c)==1)return count;
count++;
}
return -1;
}
}
2.验证回文串
熟悉一些字和字符串方法
· public static boolean isLetterOrDigit(char ch)
确定指定的字符是字母还是数字。
· public String toLowerCase()
使用默认语言环境的规则将此String所有字符转换为小写。
class Solution {
public boolean isPalindrome(String s) {
char[]chars = s.toCharArray();
int left=0;
int right=s.length()-1;
while(left<right){
while(left<right && !Character.isLetterOrDigit(chars[left]))left++;
while(left<right && !Character.isLetterOrDigit(chars[right]))right--;
if(Character.toLowerCase(chars[left])!=Character.toLowerCase(chars[right])) return false;
left++;
right--;
}
return true;
}
}
正则表达式方法:
· public String replaceAll(String regex, String replacement)
将给定替换的给定regular expression匹配的此字符串的每个子字符串替换
StringBuffer reverse()
导致此字符序列被序列的反向替换
public boolean equals(Object anObject)
将此字符串与指定的对象进行比较。 当且仅当参数不是null且是String对象表示与此对象相同的字符序列时,结果为true
eg. s.equals(t)
class Solution {
public boolean isPalindrome(String s) {
s = s.replaceAll("[^a-zA-Z0-9]","").toLowerCase();
String t=new StringBuffer(s).reverse().toString();
// if(s==t)return true;
return s.equals(t);
}
}
注意正则表达式的“反”:[^a-zA-Z0-9]
3.最长回文子串
题目:给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
思路:
以双指针由中间向两边寻找的方式,遍历字符串,找到最长的那个
难点
⭐特殊情况的去重
while(right<length-1&&s.charAt(right)==s.charAt(right+1)){//i->right 去掉left>0;
right++;
}
加入单个字符的判断
该思路其实已经省略了单字符判断:
while (right < length - 1 && left > 0 && s.charAt(right + 1) == s.charAt(left - 1)) {
++right;
--left;}
if (right - left + 1 > maxLen) {
start = left;
maxLen = right - left + 1;}
在没有添加去重之前会报错,比如“c,b,b”这样的例子
无优化:
添加优化(当字符串长度减去当前中心点的下标,如果该数没有当前最大回文数长度/2大,那就直接退出不用找了):
class Solution {
public String longestPalindrome(String s) {
int length=s.length();
int max=0;
int maxLen=0;
int start=0;
for(int i=0;i<length;){//i不自增
int left=i;
int right=i;
if (length - i <= maxLen / 2)//优化
break;
while(right<length-1&&s.charAt(right)==s.charAt(right+1)){//i->right 去掉left>0;
right++;}
i=right+1;
while (right < length - 1 && left > 0 && s.charAt(right + 1) == s.charAt(left - 1)) {
++right;
--left;
}
//保留最长的
if (right - left + 1 > maxLen) {
start = left;
maxLen = right - left + 1;
}
}
return s.substring(start,start+maxLen);
}
}
i不在for循环里自增而是改成i=right+1
可以看出没有区别
4.最长公共前缀
题目:编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “” 。
思路:选第一个字符先当公共前缀,然后缩短
class Solution {
public String longestCommonPrefix(String[] strs) {
String ans=strs[0];
if(strs.length==1)return ans;
for(String s:strs){
ans=help(ans,s);
}
return ans;
}
public String help(String s1,String s2){
int i=0;
String s="";
while(i<s1.length()&&i<s2.length()){
if(s1.charAt(i)!=s2.charAt(i))
break;
else s=s1.substring(0,++i);//一开始写了i++,输出输出少了,猜到i小了但还没搞清楚
}
return s;
}
}
不太行
另外一种写法涉及的indexOf方法indexOf() 方法有以下四种形式:
public int indexOf(int ch): 返回指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。
public int indexOf(int ch, int fromIndex): 返回从 fromIndex 位置开始查找指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。
int indexOf(String str): 返回指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。
int indexOf(String str, int fromIndex): 返回从 fromIndex 位置开始查找指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。
所以我们可以通过循环遍历String数组,以第一个字符串作为最长公共前缀,作为indexOf() 的参数,如果返回值为true,相当于得到了公共前缀,如果为false,则将参数缩短(substring(0,length-1)),直到找到或为算短到“”为止,然后继续下一个string进行匹配
public String longestCommonPrefix(String[] strs) {
//边界条件判断
if (strs == null || strs.length == 0)
return "";
//默认第一个字符串是他们的公共前缀
String pre = strs[0];
int i = 1;
while (i < strs.length) {
//不断的截取
while (strs[i].indexOf(pre) != 0)
pre = pre.substring(0, pre.length() - 1);
i++;
}
return pre;
}
String和StringBuffer
String
在代码中遇到字符串常量时,编译器会使用该值创建一个 String 对象
String 创建的字符串存储在公共池中,而 new 创建的字符串对象在堆上
String s1 = “Runoob”; // String 直接创建
String s2 =“Runoob”; // String 直接创建
String s3 = s1; // 相同引用
String s4 = new String(“Runoob”); // String 对象创建
String s5 =new String(“Runoob”); // String 对象创建
注意:String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了(详看笔记部分解析)。
如果需要对字符串做很多修改,那么应该选择使用 StringBuffer & StringBuilder 类
StringBuffer
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
三、链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
Deque <ListNode>stack = new ArrayDeque<ListNode>();
while(head!=null) {stack.push(head);head=head.next;}
if (stack.size()==0) return null;
ListNode reverse=stack.pop();
ListNode newhead=reverse;
while(stack.size()!=0) {
ListNode next=stack.pop();
// next.next=null;假设此处不添加就要在循环结束后添加,因为最后一个结点就是反转前的头结点,否则构成环
reverse.next=next;
reverse=reverse.next;
}
reverse.next=null;
return newhead;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
// Deque <ListNode>stack = new ArrayDeque<ListNode>();
Stack <ListNode> stack=new Stack<>();
while(head!=null) {stack.push(head);head=head.next;}
if (stack.isEmpty()) return null;
ListNode reverse=stack.pop();
ListNode newhead=reverse;
while(!stack.isEmpty()) {
ListNode next=stack.pop();
// next.next=null;假设此处不添加就要在循环结束后添加,因为最后一个结点就是反转前的头结点,否则构成环
reverse.next=next;
reverse=reverse.next;
}
reverse.next=null;
return newhead;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null||head.next==null) return head;
ListNode next=head.next;
ListNode reverse=reverseList(head.next);
// reverse.next=head;
next.next=head;
head.next=null;
return reverse;
}
}
class Solution {
public void deleteNode(ListNode node) {
ListNode front=node;
while(node.next!=null){
node.val=node.next.val;
node=node.next;
}
while (front.next!=null)
if(front.next!=null&&front.next.next==null)
front.next=null;
else front=front.next;
}
}
Java 集合
接口和实现类的关系
假如我们要实现一个队列的操作,首先我们要创建一个队列的抽象接口
public interface My_Queue< E >{
E remove();
void add(E element);
int size();
}
然后写具体实现,Deque有两种实现方法,一种是循环数组的形式,一种是链表的形式,所以有两种实现类:
public class Array_Queue() implements My_Queue< E >
{
private int head;
private int tail;
public void add(E element){};//写具体的实现方法
//...省略
}
public class LinkedList_Deque() implements My_Queue< E >
{
private Link head;
private Link tail;
public void add(E element){};//写具体的实现方法
//...省略
}
然后在构造集合对象中再使用具体的类,也可以及时的对他进行更改
List<Integer> list=new LinkedList<>();
Deque<TreeNode> deque=new LinkedList<>();
LinkedList 在指定位置插入的实例方法
Interface Deque< E >的实例方法
方法 | 摘要 |
---|---|
contains(Object o) | 如果此双端队列包含指定的元素,则返回 true 。 |
E peek() | 检索但是不删除头部 |
E poll() | 检索并删除此双端队列表示的队列的头部 |
E pop() | 从此双端队列表示的堆栈中弹出一个元素 |
Collection 接口
Collection 主要有两个基本方法,一个添加一个返回实现Iteractor接口的对象,使用这个对象可以遍历集合中的元素
public interface Collection<E>{
boolean add(E element);
Iterator<E> iterator();
//...
}
迭代器
public interface iterator<E>{
E next();
boolean haveNext();
void remove();
//...
}
remove方法会删除上次调用的next方法返回的元素
remove之前必须要调用next
集合框架接口
List集合的访问可以通过迭代器或整数索引来访问
有几种随机访问的方法
void add(int index,E element)
E get (int index)
//...
散列集
用于快速查找对象,散列表为每个对象计算一个整数称为散列码
散列表用链表数组实现,每个列表被称为桶
查找位置:先计算散列码在和桶的总数取余
假如要插入一个元素但是桶已经被填充了,此时就要遍历那个通看看这个元素是不是已经存在了
树
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode helper(int inoStart,int inoEnd,int preStart,int[] preorder, int[] inorder){
if(inoStart > inoEnd || preStart>preorder.length-1)
return null;
TreeNode root=new TreeNode(preorder[preStart]);
int index=0;//用在中序
for(int num:inorder){
if (num==root.val)
break;
index++;
}
root.left=helper(inoStart,index-1,preStart+1,preorder,inorder);
root.right=helper(index+1,inoEnd,preStart+index-inoStart+1,preorder,inorder);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
return helper(0,inorder.length-1,0,preorder,inorder);
}
}
递归来拆分子树。前序遍历的第一个必是根节点,在中序序列里找到那个结点进行拆分,再找下一个根节点。⭐如何找到下一个根节点?
举例:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
第一次拆分: [ 9 ] 3 [ 15,20,7 ] (根节点:3)
第二次拆分(先左): [ 9 ] (根节点:9)
root.left = helper(...,...,...);
//⭐怎样通过下标进行拆分?
通过整个树的下标划分出小树的中序序列,设置参数 int inStart 和 inEnd
以及小树先序序列起始的地方也就是根节点,设置参数为 int preStart
(分割左右的进阶版)
preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
所以说在第一次拆分时,root.left=helper(…)参数,可以看出参数为:
inStart =inStart
inEnd=index-1
root.right=helper(…)参数,可以看出参数为:
inStart = index+1
inEnd = inEnd
//⭐怎样确定在preorder序列上的指针
首先这个递归方法目的是从最小的子树开始搭建,也就是寻找根结点(null<-A->null),先从先序遍历找到根节点(第一个),再在中序遍历找到根节点,划分完成后,再通过下标找到先序遍历中下一个操作的根节点。由图可知处理玩结点3下一个要处理结点9,此时先序的prestart应该指向9
如何确定preStart
left的preStart=preStart+1 由根左右可知,左一定在根的下一个
right的preStart
由 根左右 左根右 以及小树的中序序列可知,preStart指向右节点,右节点在先序遍历的位置是当前的根下标+[根左]的宽度,如何确定[根左]的长度,可以在中序遍历中(根的下标) - (左下标)得到
也就是
preStart+(index-inoStart)+1
第二种写法
思路:其实也是在中序找结点,利用ist的查找方法,建立
List<Integer> inorderList= new ArrayList<>();
//...
利用查找方法找,然后切割
//前序遍历的第一个值就是根节点
查找和切割方法:
indexOf(int i)
subList(int start,int end)
从前序序列移出第一个元素并返回值的方法:
remove(0)
DFS BFS
电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
class Solution {
public List<String> letterCombinations(String digits) {
char[][] tab = {{'a', 'b', 'c'}, {'d', 'e', 'f'}, {'g', 'h', 'i'},
{'j', 'k', 'l'}, {'m', 'n', 'o'}, {'p', 'q', 'r', 's'},
{'t', 'u', 'v'}, {'w', 'x', 'y', 'z'}};
List<String> ans=new LinkedList<>();
if (digits.length()==0||digits==null) return ans;
//if (digits==""||digits==null) return ans;
dfs(digits,tab,0,"",ans);
return ans;
}
public void dfs(String digits,char[][]tab,int index,String path,List<String>ans){
if(path.length()==digits.length())
{ans.add(path);return;}
char[] chars=tab[digits.charAt(index)-'2'];
for(int i=0;i<chars.length;i++)
dfs(digits,tab,index+1,path+chars[i],ans);
}
}
注释那里会出现下面这个错误
来自:http://t.csdn.cn/CS0QL
null就是空,不占用内存空间,没有任何属性,也不能读取属性,即没有.length()等;
“” :表示string指向一个长度为0的字符串对象
str.length() = 0,此刻是一个字符串,已经为其分配了一定的内存空间
讲解String类:
https://www.bbsmax.com/A/ke5jmvg7Jr/
笔记写在另外一篇
五、回溯
拓号
第一次用的String类进行poll操作(substring),但这样会浪费很多内存
class Solution {
public List<String> generateParenthesis(int n) {
List<String> ans=new LinkedList<>();
help(n,0,0,ans,"");
return ans;
}
public void help(int n,int right,int left,List<String>ans,String s){
if(s.length()==n*2)
{ans.add(s);return;}
if(left<n){
s+='(';
help(n,right,left+1,ans,s);
// s = s.replaceFirst(".$", "");
s=s.substring(0,s.length()-1);
}
if(right<left){
s+=")";
help(n,right+1,left,ans,s);
s=s.substring(0,s.length()-1);
}
}
}
改用StringBuilder
class Solution {
public List<String> generateParenthesis(int n) {
List<String> ans=new LinkedList<>();
StringBuilder sb=new StringBuilder();
help(n,0,0,ans,sb);
return ans;
}
public void help(int n,int right,int left,List<String>ans,StringBuilder sb){
if(sb.length()==n*2)
{ String s=sb.toString();
ans.add(s);return;}
if(left<n){
// s+='(';
sb.append("(");
help(n,right,left+1,ans,sb);
sb.delete(sb.length()-1,sb.length());
// s = s.replaceFirst(".$", "");
// s=s.substring(0,s.length()-1);
}
if(right<left){
sb.append(")");
help(n,right+1,left,ans,sb);
sb.delete(sb.length()-1,sb.length());
}
}
}
StringBuffer和StringBuilder的区别
StringBuffer和StringBuilder都是可变字符串类,但它们有以下区别:
线程安全性:StringBuffer是线程安全的,而StringBuilder是非线程安全的。
性能:StringBuilder比StringBuffer性能更好,因为它不考虑线程安全性,不需要进行同步操作。
应用场景:当需要进行多线程操作时,应该使用StringBuffer,因为它是线程安全的。当不需要考虑线程安全时,应该使用StringBuilder,因为它性能更好。
StringBuilder的常见用法
创建一个StringBuilder对象:
StringBuilder sb = new StringBuilder();
向StringBuilder中添加字符串:
sb.append("hello");
插入字符串:
sb.insert(0,"world ");
删除字符串:
sb.delete(5,10);
替换字符串:
sb.replace(5,10,"world");
获取字符串长度:
sb.length();
获取字符串内容:
sb.toString();
清空字符串:
sb.setLength(0);