基本知识
StringBuilder&StringBuffer&&String
StringBuilder类也代表可变字符串对象。实际上,StringBuilder和StringBuffer基本相似,两个类的构造器和方法也基本相同。不同的是:StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高。
一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象。
String类是不可变类,即一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。
String a = "123";
a = "456";
// 打印出来的a为456
System.out.println(a)
可以看出来,再次给a赋值时,并不是对原来堆中实例对象进行重新赋值,而是生成一个新的实例对象,并且指向“456”这个字符串,a则指向最新生成的实例对象,之前的实例对象仍然存在,如果没有被再次引用,则会被垃圾回收。
Map.getOrDefault(Object key, V defaultValue)
Map.getOrDefault(Object key, V defaultValue)方法的作用是:
当Map集合中有这个key时,就使用这个key值;
如果没有就使用默认值defaultValue。
HashMap<String, String> map = new HashMap<>();
map.put("name", "cookie");
map.put("age", "18");
map.put("sex", "女");
String name = map.getOrDefault("name", "random");
System.out.println(name);// cookie,map中存在name,获得name对应的value
int score = map.getOrDefault("score", 80);
System.out.println(score);// 80,map中不存在score,使用默认值80
双目移位运算符 >>1 和 /2 哪个更快?
速度很可能是一样快。建议,不要使用移位代替乘除。
Stack.peek()和Stack.pop()的区别
- Stack.peek()
peek()函数返回栈顶的元素,但不弹出该栈顶元素。
- Stack.pop()
pop()函数返回栈顶的元素,并且将该栈顶元素出栈。
Map集合中不应该利用get()方法来判断是否存在某个键,而应该利用containsKey()方法来判断
LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作
交换和反转
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public void reverse(int[] nums, int start) {
int left = start, right = nums.length - 1;
while (left < right) {
swap(nums, left, right);
left++;
right--;
}
}
new String()和toString()的区别
str.toString是调用了该对象的类的toString方法。一般是返回这么一个String:[class name]@[hashCode]。
new String(str)是使用java虚拟机默认的编码格式,将这个字节数组转换为对应的字符。若虚拟机默认的编码格式是ISO-8859-1,按照ascii编码表即可得到字节对应的字符。
new String()一般用在将字节数组转为字符串的时候
toString()一般用在输出某个对象的时候
问题(来源于leetcode):
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map=new HashMap<String, List<String>>();
for(String str:strs){
char[] array=str.toCharArray();
Arrays.sort(array);//传数组 先排序
// String key=array.toString();不能用这个???
String key=new String(array);
List<String> list=map.getOrDefault(key,new ArrayList<String>());
list.add(str);
map.put(key,list);
}
return new ArrayList<List<String>>(map.values());
测试:
char[] array={'a','b'};
String key = array.toString();//[C@1d56ce6a 地址 每次地址都不一样 key当然也不会一样 因此会有这样如下测试结果
System.out.println(key);
String key2 = new String(array);//ab 值
System.out.println(key2);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WTDItSef-1654156134512)(C:\Users\50537\AppData\Roaming\Typora\typora-user-images\image-20220519114000686.png)]
关于Array和List的区别
1.Array连续 List不连续 因此定义时需要指定Array长度
2.查找Array 插入List
动态规划
动态规划的三大步骤
利用历史记录,来避免重复计算。而这些历史记录,我们得需要一些变量来保存,一般是用一维数组或者**二维数组来保存。
第一步骤:定义数组元素的含义**,用一个数组,来保存历史数组,假设用一维数组 dp[] 你的 dp[i] 是代表什么意思?
第二步骤:找出数组元素之间的关系式
第三步骤:找出**初始值**。
判断数组等数据结构不越界需要放在条件的前面
如
else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(')
反过来就会报错 必须保证先不出界才能继续后面条件判断双指针
这个方法就是我们常说的「双指针」,当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,
将给定数字序列重新排列成字典序中下一个更大的排列。
爬楼梯
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。 每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
通项公式: f(x)=f(x−1)+f(x−2) 滚动数组
public int climbStairs(int n) {
// 动态规划 滚动数组 初始值 通项公式
int p = 0, q = 0, r = 1;//第0层
for (int i = 1; i <= n; i++) {
p = q;
q = r;
r = p + q;
}
return r;
}
/* 动态规划五部曲:
* 1.确定dp[i]的下标以及dp值的含义: 爬到第i层楼梯,有dp[i]种方法;
* 2.确定动态规划的递推公式:dp[i] = dp[i-1] + dp[i-2];
* 3.dp数组的初始化:因为提示中,1<=n<=45 所以初始化值,dp[1] = 1, dp[2] = 2;
* 4.确定遍历顺序:分析递推公式可知当前值依赖前两个值来确定,所以递推顺序应该是从前往后;
* 5.打印dp数组看自己写的对不对;
*/
回溯
def backtrack(...):
for 选择 in 选择列表:
做选择
backtrack(...)
撤销选择
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
例题1
if的两个选择 括号匹配
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VLwaKcgu-1654156134515)(C:\Users\50537\AppData\Roaming\Typora\typora-user-images\image-20220420170037385.png)]
例题2
//给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的
// 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> ans=new ArrayList<>();
List<Integer> cur=new ArrayList<>();
backtrack(candidates,target,ans,cur,0,0);
return ans;
}
public void backtrack(int[] candidates,int target,List<List<Integer>> ans,List<Integer> cur,int sum,int index) {
if (sum == target) {
ans.add(new ArrayList<Integer>(cur));
//此处为啥不是ans.add(cur)?
//答案:因为cur是数组的引用地址,直接add最后是空的
return;
}
if (sum < target) {
//index的作用是为了使得不出现 测试结果:[[2,2,3],[2,3,2],[3,2,2],[7]] 期望结果:[[2,2,3],[7]]
//这种情况,保持数组cur升序,也就避免了重复
for (int i = index; i < candidates.length; i++) {
cur.add(candidates[i]);
sum = sum + candidates[i];
backtrack(candidates, target, ans, cur, sum,i);//i传进去
cur.remove(cur.size() - 1);
sum = sum - candidates[i];//剪枝操作 返回回原来状态 否则sum只会越来越大
}
}
}
}
例题3
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aEvGgExB-1654156134515)(C:\Users\50537\AppData\Roaming\Typora\typora-user-images\image-20220531161347850.png)]
vector<int> t;
void dfs(int cur, int n) {
if (cur == n) {
// 记录答案
// ...
return;
}
// 考虑选择当前位置
t.push_back(cur);
dfs(cur + 1, n, k);
t.pop_back();
// 考虑不选择当前位置
dfs(cur + 1, n, k);
}
递归
/**
* 合并两有序链表 递归方法 代码少 但效率不行
*/
public ListNode mergeTwoLists(ListNode list1,ListNode list2){
if(list1==null||list2==null){
return list1==null?list2:list1;
}else if(list1.val<list2.val){
list1.next=mergeTwoLists(list1.next,list2);
return list1;
}else{
list2.next=mergeTwoLists(list2.next,list1);
return list2;
}
}
/**
* 普通方法 代码多 但效率快
*/
/*
public ListNode mergeTwoLists(ListNode list1,ListNode list2){
if(list1==null||list2==null){
return list1==null?list2:list1;
}
ListNode head=new ListNode(0);
ListNode tail=head,aptr=list1,bptr=list2;
while (list1!=null&&list2!=null){
if(list1.val<list2.val){
tail.next=list1;
list1=list1.next;
}else{
tail.next=list2;
list2=list2.next;
}
tail=tail.next;
}
tail.next=list1!=null?list1:list2;
return head.next;
}
*/
数学、规律
通项公式
力扣48题翻转矩阵
只关注我画黑框的部分 其余三块都是每个元素旋转而成
// 官方第二种方法
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
for (int i = 0; i < n / 2; ++i) {//行数 向下区取余
for (int j = 0; j < (n + 1) / 2; ++j) {//列数 加一再向下取余
int temp = matrix[i][j];
matrix[i][j] = matrix[n - j - 1][i];
matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
matrix[j][n - i - 1] = temp;
}
}
}
}
最终解法 将旋转分解为水平翻转和主对角线翻转(因为这俩翻转都是俩俩交换 直接temp换就完事儿,而旋转是4个元素在换)
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
// 水平翻转
for (int i = 0; i < n / 2; ++i) {
for (int j = 0; j < n; ++j) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - i - 1][j];
matrix[n - i - 1][j] = temp;
}
}
// 主对角线翻转
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
}
二分
部分有序也可以二分
旋转数组:
将数组一分为二,其中一定有一个是有序的,另一个可能是有序,也能是部分有序。
此时有序部分用二分法查找。无序部分再一分为二,其中一个一定有序,另一个可能有序,可能无序。就这样循环.
小技巧
一些变量符号
表示数量的话用cnt
flag表示标志,一般为正负
IDEA插件LeetEditor
出现cookie问题 怎么办?在idea进行插件更新!这是没想到的!
temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
}
## 二分
部分有序也可以二分
```yaml
旋转数组:
将数组一分为二,其中一定有一个是有序的,另一个可能是有序,也能是部分有序。
此时有序部分用二分法查找。无序部分再一分为二,其中一个一定有序,另一个可能有序,可能无序。就这样循环.
小技巧
一些变量符号
表示数量的话用cnt
flag表示标志,一般为正负
IDEA插件LeetEditor
出现cookie问题 怎么办?在idea进行插件更新!这是没想到的!