1 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。你可以按任意顺序返回答案。
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
思路:
1 创建一个HashMap集合,key待变数组的值,value代表数组的索引,利用map.containsKey()方法进行判断,判断map集合中是否存在与nums[i]互补的值。
2 对数组进行遍历,
map.containsKey(target-nums[i]) map中是否存在 target-nums[i]的key
if(存在){
就得到答案
}else if(不存在){
就把nums[i]放进map的key中,i放在value中
}
import java.util.Map;
import java.util.HashMap;
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer ,Integer> map=new HashMap<Integer,Integer>();
for(int i=0;i<nums.length;i++){
if(map.containsKey(target-nums[i])){
return new int[]{
map.get(target-nums[i]),i};
}
map.put(nums[i],i);
}
return new int[]{
-1,-1};
}
}
1299 给你一个数组 arr ,请你将每个元素用它右边最大的元素替换,如果是最后一个元素,用 -1 替换。完成所有替换操作后,请你返回这个数组。
示例
输入:arr = [17,18,5,4,6,1]
输出:[18,6,6,6,1,-1]
思路:
1 对数组从右向左进行遍历
for(){
2 用一个辅助变量保存当前元素的值
3 当前的arr[i]=右侧最大值
4 计算下次循环的当前最大值
}
class Solution {
public int[] replaceElements(int[] arr) {
int max=arr[arr.length-1];
int temp=0;
for(int i=arr.length-1;i>=0;i--){
//将用一个辅助节点取值
temp=arr[i];
//将本节点变成右侧最大值
arr[i]=max;
//计算右侧最大值包括自身
max=(temp>=arr[i])?temp:arr[i];
}
arr[arr.length-1]=-1;
return arr;
}
}
1464 给你一个整数数组 nums,请你选择数组的两个不同下标 i 和 j,使 (nums[i]-1)*(nums[j]-1) 取得最大值。请你计算并返回该式的最大值。
输入:nums = [3,4,5,2]
输出:12
解释:如果选择下标 i=1 和 j=2(下标从 0 开始),则可以获得最大值,(nums[1]-1)*(nums[2]-1) = (4-1)*(5-1) = 3*4 = 12
思路:
1 利用hashmap去重,把nums[i]保存在key中,index保存在value中
2 定义一个二维数组result保存结果
3 遍历hashmap找到最大值,把nums和i保存在result中
4 移除最大值
5 再次遍历map找到最大值,把nums和i保存在result中
6 根据result中的结果进行计算
class Solution {
public int maxProduct(int[] nums) {
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
//先利用hashmap去重,key保存nums[i]-1,value保存索引
for(int i=0;i<nums.length-1;i++){
map.put(nums[i],i);
}
//定义一个结果二维数组,保存结果
int[][] result=new int[2][2];
//进行第一次遍历,获取最大值
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
if(entry.getKey()>=result[0][0]){
result[0][0]=entry.getKey();
result[0][1]=entry.getValue();
}
}
//移除最大值
map.remove(result[0][0]);
//进行第二次遍历
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
if(entry.getKey()>=result[1][0]){
result[1][0]=entry.getKey();
result[1][1]=entry.getValue();
}
}
//根据result计算
return (nums[result[0][1]]-1)*(nums[result[1][1]]-1);
}
}
15 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
思路1 :暴力排序 会超时
1 先将nums进行排序
2 三层for循环
for(int i=0;i<=nums.length-3;i++){
for(int j=i+1;j<=nums.length-2;j++){
for(int k=j+1;k<=nums.length-1;k++){
3 判断nums[i]+nums[j]+nums[k]==0
4 存在 放入list集合
5 把list集合放入HashSet集合中去重
}
}
}
6 把Set集合装换成List<List>集合
思路2 :排序+双指针
1 先进行排序
2 用三个指针遍历数组,k 、 i=k+1 、 j=nums.length-1
3 k不动,i、j向中间移动
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
//1 先进行排序
Arrays.sort(nums);
//2 创建list集合返回结果
List<List<Integer>> result=new ArrayList<>();
//3 对数组进行遍历
for(int k=0;k<nums.length-2;k++){
//4 如果nums[k]>0,则三数之和必大于0
if(nums[k]>0){
break;
}
//5 如果nums[k]==nums[k-1] 则跳过这层循环
if(k>0&&nums[k]==nums[k-1]){
continue;
}
//6 初始化i、j指针
int i=k+1,j=nums.length-1;
//7 移动i、j指针
while(i<j){
//8 求和
int sum=nums[k]+nums[i]+nums[j];
//9 sum<0 ,i向右移动 i++
if(sum<0){
//10 移动i,并且去重,直到nums[i]!=nums[++i];
while(i<j&&nums[i]==nums[++i]);//去重
} else if(sum>0){
//11 移动j,并去重
while(i<j&&nums[j]==nums[--j]);
}else if(sum==0){
//12 添加结果
result.add(new ArrayList<Integer>(Arrays.asList(nums[k],nums[i],nums[j])));
//13 i、j同时向中间移动
while(i < j && nums[i] == nums[++i]);
while(i < j && nums[j] == nums[j-1]){
j=j-1;
}
}
}
}
// 14 返回结果
return result;
}
}
给定一组非负整数 nums,重新排列它们每个数字的顺序(每个数字不可拆分)使之组成一个最大的整数。注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
输入:nums = [10,2]
输出:"210"
思路:
1 重写排序 实现Comparator接口
2 重写接口方法
3 Arrays.sort(asStrs, new LargerNumberComparator());匿名内部类
进行排序
4 字符串拼接
class Solution {
private class LargerNumberComparator implements Comparator<String> {
//降序排列
@Override
public int compare(String a, String b) {
String order1 = a + b;//如果ab>ba -1 降序 排序结果 ab
String order2 = b + a;//如果ba>ab 1 升序 排序结果 ba
return order2.compareTo(order1);
}
}
public String largestNumber(int[] nums) {
// Get input integers as strings.
String[] asStrs = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
asStrs[i] = String.valueOf(nums[i]);
}
// Sort strings according to custom comparator.
Arrays.sort(asStrs, new LargerNumberComparator());
// If, after being sorted, the largest number is `0`, the entire number
// is zero.
if (asStrs[0].equals("0")) {
return "0";
}
// Build largest number from sorted array.
String largestNumberStr = new String();
for (String numAsStr : asStrs) {
largestNumberStr += numAsStr;
}
return largestNumberStr;
}
}
189 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
进阶
尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。你可以使用空间复杂度为O(1)的原地算法解决这个问题吗?
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
思路一: 使用额外数组
1创建一个等长的数组
2把元素放在正确的位置上 i+k%n
3用 System.arraycopy() 方法复制过来
class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
int[] newArr = new int[n];
for (int i = 0; i < n; ++i) {
newArr[(i + k) % n] = nums[i];
}
//其中:src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,length表示要复制的长度
System.arraycopy(newArr, 0, nums, 0, n);//把newArr中的元素复制道nums中
}
}
思路二:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h6HlYdWN-1633515954133)(D:\学习笔记\刷题笔记截图\image-20210414104901897.png)]
class Solution {
//根据k进行翻转
public void rotate(int[] nums, int k) {
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
//编写翻转数组方法
public void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start += 1;
end -= 1;
}
}
}
215 在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
思路1:
1 调用Arrays.sort()先对数组排序 默认升序
2 返回倒数第k个元素
另一种思路:
1 进行k次循环,每次把最大值置为最小值 Integer.MIN_VALUE
2 第k次循环 返回最大值
class Solution {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
if(k>nums.length){
return 0;
}else{
return nums[nums.length-k];
}
}
}
思路二:堆排序
用一个长度为k小顶堆
依次往小顶堆中添加元素 如果元素个数大于k就弹出堆顶的最小的元素
那么到最后 堆中留下的就是最大的k个数 堆顶就是k个数中最小的那个数
import java.util.PriorityQueue;
class Solution {
int findKthLargest(int[] nums, int k) {
// 小顶堆,堆顶是最小元素
PriorityQueue<Integer>
pq = new PriorityQueue<>();
for (int e : nums) {
// 每个元素都要过一遍二叉堆
pq.offer(e);
// 堆中元素多于 k 个时,删除堆顶元素
if (pq.size() > k) {
pq.poll();
}
}
// pq 中剩下的是 nums 中 k 个最大元素,
// 堆顶是最小的那个,即第 k 个最大元素
return pq.peek();
}
}
思路三:
快速插入排序
public class Solution {
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
int left = 0;
int right = len - 1;
// 转换一下,第 k 大元素的索引是 len - k
int target = len - k;
while (true) {
int index = partition(nums, left, right);
if (index == target) {
return nums[index];
} else if (index < target) {
left = index + 1;
} else {
right = index - 1;
}
}
}
/**
* 在数组 nums 的子区间 [left, right] 执行 partition 操作,返回 nums[left] 排序以后应该在的位置
* 在遍历过程中保持循环不变量的语义
* 1、[left + 1, j] < nums[left]
* 2、(j, i] >= nums[left]
*
* @param nums
* @param left
* @param right
* @return
*/
public int partition(int[] nums, int left, int right) {
int pivot = nums[left];
int j = left;
for (int i = left + 1; i <= right; i++) {
if (nums[i] < pivot) {
// 小于 pivot 的元素都被交换到前面
j++;
swap(nums, j, i);
}
}
// 在之前遍历的过程中,满足 [left + 1, j] < pivot,并且 (j, i] >= pivot
swap(nums, j, left);
// 交换以后 [left, j - 1] < pivot, nums[j] = pivot, [j + 1, right] >= pivot
return j;
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
239 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
思路1: 该思路超时不可行
1 滑动次数nums.length-k+1
2 进行nums.length-k+1遍历,每次遍历代表一次滑动
3 将每次滑动要比较的数放在一个数组中,//对数组进行排序(可用多种排序)//或者取最大值
4 取出最大值依次放在result数组中
排序法
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//定义结果
int[] result=new int[nums.length-k+1];
//滑动次数为nums.length-k+1
int num=nums.length-k+1;
//进行nums.length-k+1遍历 每遍历一次取一次最大值
for(int i=0;i<num;i++){
int[] temp1=new int[k];
//第i次滑动需要比较的数组
for(int j=0;j<temp1.length;j++){
temp1[j]=nums[i+j];
}
result[i]=max(temp1);
}
return result;
}
//定义求最大值函数
public int max(int[] arr){
Arrays.sort(arr);
int a=arr.length;
return arr[a-1];
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//定义结果
int[] result=new int[nums.length-k+1];
//滑动次数为nums.length-k+1
int num=nums.length-k+1;
//进行nums.length-k+1遍历 每遍历一次取一次最大值
for(int i=0;i<num;i++){
int[] temp1=new int[k];
//第i次滑动需要比较的数组
int aa=Integer.MIN_VALUE;
for(int j=0;j<temp1.length;j++){
if(aa<nums[i+j]){
aa=nums[i+j];
}
}
result[i]=aa;
}
return result;
}
}
思路2:
对于两个相邻(只差了一个位置)的滑动窗口,它们共用着 k-1个元素,而只有1个元素是变化的。我们可以根据这个特点进行优化。
使用优先队列
1 创建优先队列,自定义排序顺序,队列中放值的元素为 int[](数组的值和索引)
2 排序规则 先比较数组值降序排列,如果相等 索引降序排列
3 队列顶即最大值,即结果
4 每滑动一次,就加进来一个数,同时判断顶值索引是否在新窗口内,在就不用管,不在就要删除
5 再次返回顶值 放在结果集中
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//求出数组长度
int n = nums.length;
//创建优先队列,自定义比较方式
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] pair1, int[] pair2) {
return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
}
});
//往队列中添加元素
for (int i = 0; i < k; ++i) {
pq.offer(new int[]{
nums[i], i});
}
//定义一个n-k+1的数组 用于存放结果
int[] ans = new int[n - k + 1];
//获取队列头
ans[0] = pq.peek()[0];
//遍历
for (int i = k; i < n; ++i) {
//添加新元素进去
pq.offer(new int[]{
nums[i], i});
//保证优先队列中的最大值 是在滑动窗口范围内的
//如果上次最大值索引不在这次滑动窗口内,就移除
while (pq.peek()[1] <= i - k) {
//删除队列头
pq.poll();
}
//把本次结果放在结果数组中
ans[i - k + 1] = pq.peek()[0];
}
//返回结果
return ans;
}
}
思路3
单调栈
每次往栈中压一个数 从尾部把栈中比自己小的数都弹出 再压进去
队列中始终保持降序排列
但是排序方式是移除比自己小的数 和 已经不再窗口中但是还位于队列头的元素
import java.util.ArrayDeque;
import java.util.ArrayList;
class Solution {
//内部类
private class MonotonicQueue{
ArrayDeque<Integer> deque;
public MonotonicQueue(){
//初始化一个双端队列
deque=new ArrayDeque<Integer>();
}
//获取队列中的最大的元素
public Integer max(){
return deque.peekFirst();
}
//弹出上一次窗口的中第一个元素
//如果位于队列头就弹出 否则就不弹出
public void pop(int num){
if(!deque.isEmpty() && num==deque.peekFirst())
deque.pollFirst();
}
//把元素压入堆栈 如果队列尾部小于要压入的元素 就弹出
//直到队列为空 或者队列尾部元素大于要压入的元素
public void push(int num){
while(!deque.isEmpty() && deque.peekLast()<num){
//移除尾部比自己小的元素
//保证队列中的元素是降序排列
deque.pollLast();
}
//将该元素添加道队列尾部
deque.addLast(num);
}
}
public int[] maxSlidingWindow(int[] nums, int k) {
MonotonicQueue windows = new MonotonicQueue();
ArrayList<Integer> res = new ArrayList<>();
for(int i=0;i<nums.length;i++){
if(i < k - 1){
//初始化窗口
windows.push(nums[i]);
}else{
//先在窗口中添加一个元素
windows.push(nums[i]);
//结果集中添加最大的元素
res.add(windows.max());
//弹出第一个元素 如果存在就弹出 不存在就忽略
windows.pop(nums[i-k+1]);
}
}
int[] ans = new int[res.size()];
for(int i=0;i<ans.length;i++){
ans[i] = res.get(i);
}
return ans;
}
}
33 整数数组 nums 按升序排列,数组中的值互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的索引,否则返回 -1
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
思路: 双向遍历
1 先根据数组大小 确定使用什么遍历方式
2 数组小 使用普通 数组大 使用两端
3 根据target的值和nums[i]的比较确定从哪头进行比较
class Solution {
public int search(int[] nums, int target) {
//1如果数组长度小于100,则普通遍历即可
if(nums.length<100){
for(int i=0;i<nums.length;i++){
if(nums[i]==target){
return i;
}
}
return -1;
}else{
//2如果数组长度大于100
//3判断从那边遍历
if(target>=nums[0]){
//4从左边
//先比较第一个数
if(nums[0]==target){
return 0;
}
int i=1;
//从第二个数开始遍历,直到翻转的位置
while(nums[i]<target&& i<nums.length && nums[i]>nums[i-1]){
i++;
}
//如果nums[i]=target 则找到,不等于则没有找到
if(nums[i]!=target){
return -1;
}else{
return i;
}
}else{
//从右边
if(nums[nums.length-1]==target){
return nums.length-1;
}
int i=nums.length-2;
while(nums[i]>target&& i>=0 &&nums[i]<nums[i+1]){
i--;
}
if(nums[i]!=target){
return -1;
}else{
return i;
}
}
}
}
}
思路2 :搜索旋转数组
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
if (n == 0) {
return -1;
}
if (n == 1) {
return nums[0] == target ? 0 : -1;
}
int l = 0, r = n - 1;
while (l <= r) {
int mid = (l + r) / 2;
if (nums[mid] == target) {
return mid;
}
if (nums[0] <= nums[mid]) {
if (nums[0] <= target && target < nums[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
} else {
if (nums[mid] < target && target <= nums[n - 1]) {
l = mid + 1;
} else {
r = mid - 1;
}
}
}
return -1;
}
}
思路3:
1 找到中间点
2 分成两个有序数组
3 遍历数组
349 给定两个数组,编写一个函数来计算它们的交集。
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
说明:
输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序。
思路:
1 将两个数组放到hashset中去重
2 用小数组 遍历大数组 用contains方法判断是否有交集
3 将交集放在hashset中去重
4 将hashset转换成int数组
5 返回结果
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> set1 = new HashSet<Integer>();
Set<Integer> set2 = new HashSet<Integer>();
for (int num : nums1) {
set1.add(num);
}
for (int num : nums2) {
set2.add(num);
}
return getIntersection(set1, set2);
}
public int[] getIntersection(Set<Integer> set1, Set<Integer> set2) {
//小数组驱动大数组
if (set1.size() > set2.size()) {
return getIntersection(set2, set1);
}
Set<Integer> intersectionSet = new HashSet<Integer>();
for (int num : set1) {
if (set2.contains(num)) {
intersectionSet.add(num);
}
}
int[] intersection = new int[intersectionSet.size()];
int index = 0;
for (int num : intersectionSet) {
intersection[index++] = num;
}
return intersection;
}
}
350 给定两个数组,编写一个函数来计算它们的交集。
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
我们可以不考虑输出结果的顺序。
方法一:哈希表
由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。
首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。
为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
//小数组驱动大数组
if (nums1.length > nums2.length) {
return intersect(nums2, nums1);
}
//创建map集合 第一个数保存值 第二个数保存次数
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
//将num1处理后保存在map中
for (int num : nums1) {
int count = map.getOrDefault(num, 0) + 1;
map.put(num, count);
}
//定义一个数组
int[] intersection = new int[nums1.length];
int index = 0;
for (int num : nums2) {
int count = map.getOrDefault(num, 0); //返回 指定键所包含的映射值 或者 defaultValue
//如果map中的个数>0 说明是交集
if (count > 0) {
//把该元素放在数组中
intersection[index++] = num;
//同时 map中的数量-1
count--;
//如果map中该元素的数量还大于0 就更新数据
if (count > 0) {
map.put(num, count);
//否则 移除该数据
} else {
map.remove(num);
}
}
}
//返回结果
return Arrays.copyOfRange(intersection, 0, index);
}
}
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
class Solution {
public int findRepeatNumber(int[] nums) {
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
for(int i=0;i<nums.length;i++){
if(map.containsKey(nums[i])){
return nums[i];
}else{
map.put(nums[i],1);
}
}
return 0;
}
}
原地交换
用一个长度为n的数组,索引和值一一对应,不对应就交换,对应则不用交换
class Solution {
public int findRepeatNumber(int[] nums) {
int[] arr=new int[nums.length];
for(int i:arr){
arr[i]=-1;
}
for(int i=0;i<nums.length;i++){
if(arr[nums[i]]==nums[i]){
return nums[i];
}else{
arr[nums[i]]=nums[i];
}
}
return -1;
}
}
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 matrix 如下:
[
[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
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
int i = matrix.length - 1, j = 0;
while(i >= 0 && j < matrix[0].length){
if(matrix[i][j] > target) i--;
else if(matrix[i][j] < target) j++;
else return true;
}
return false;
}
}
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
int i=0,j=array.length-1;
while(i<j){
int m=(i+j)/2;
if(array[m]>array[j]) i=m+1;
else if(array[m]<array[j]) j=m;
else j--;
}
return array[i];
}
}
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。
提示:
0 <= nums.length <= 50000
1 <= nums[i] <= 10000
思路:
使用快排
前后两个指针,分别判断是否为奇数和偶数,然后交换
class Solution {
public int[] exchange(int[] nums) {
if(nums.length==1) return nums;
int head=0;
int last=nums.length-1;
while(head<last){
while(head<last && (nums[head]%2)==1){
head++;
}
while(head<last && (nums[last]&1%2)==0){
last--;
}
if(head<last){
int temp=nums[last];
nums[last]=nums[head];
nums[head]=temp;
last--;
head++;
}
}
return nums;
}
}
剑指 Offer 29. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
限制:
0 <= matrix.length <= 100
0 <= matrix[i].length <= 100
思路:
按层打印,需要根据每层的四个顶点,确定边界
左上(top,left) 右上 (top,right) 左下(bottom,left) 右下(bottom,left)
时间复杂度:O(mn)
空间复杂度:O(1)
class Solution {
public int[] spiralOrder(int[][] matrix) {
//矩阵为null 长为0 宽为0 都返回0 无法遍历
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return new int[0];
}
//获取行 获取列
int rows = matrix.length, columns = matrix[0].length;
//创建保存结果的一维数组
int[] order = new int[rows * columns];
//index用来表示order数组中的索引,最开始往0的位置放
int index = 0;
//定义四个顶点的坐标 左上(top,left) 右上 (top,right) 左下(bottom,left) 右下(bottom,left)
int left = 0, right = columns - 1, top = 0, bottom = rows - 1;
while (left <= right && top <= bottom) {
//第一层判断 上面那条边
for (int column = left; column <= right; column++) {
order[index++] = matrix[top][column];
}
//右边那条边
for (int row = top + 1; row <= bottom; row++) {
order[index++] = matrix[row][right];
}
//此时考虑 一条直线的情况 如果是一条直线 经过上面两次 就不用在遍历了 left
if (left < right && top < bottom) {
//最下面一条边的遍历
for (int column = right - 1; column > left; column--) {
order[index++] = matrix[bottom][column];
}
//最左侧一条边的遍历
for (int row = bottom; row > top; row--) {
order[index++] = matrix[row][left];
}
}
//修改四个顶点的值
left++;
right--;
top++;
bottom--;
}
//返回结果
return order;
}
}
剑指 Offer 39. 数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
限制:
1 <= 数组长度 <= 50000
//常规思路 计算每个元素出现的次数 时间复杂度O(n)
class Solution {
public int majorityElement(int[] nums) {
if(nums.length==1) return nums[0];
if(nums.length==0&&nums==null) return 0;
HashMap<Integer,Integer> res=new HashMap<Integer,Integer>();
for(int i:nums){
if(res.containsKey(i)){
res.put(i,res.get(i)+1);
if(res.get(i)>nums.length/2) return i;
}else{
res.put(i,1);
}
}
return 0;
}
}
//排序 超过一半的次数 一定出现在中间
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length/2];
}
}
剑指 Offer 40. 最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000
Arrays.sort() //底层就是快速排序 从小到大
//从大到小 排列代码如下
public class SortTest {
public static void main(String[] args) {
Integer[] arr = {
4, 6, 3, 9, 1, 5, 8};
Comparator<Integer> c = new Mycomparator(); // 实例化一个Comparator对象
Arrays.sort(arr, c);
for(Integer ele : arr) {
System.out.print(ele +" ");
}
}
// 运行后是从大到小排好序的
}
//Comparator 比较两个数的大小 参数有两个数
class Mycomparator implements Comparator<Integer>
{
@Override
public int compare(Integer o1, Integer o2) {
if(o1 > o2) // 默认是o1 < o2时返回-1, 一下同理
return -1;
if(o1 < o2)
return 1;
return 0;
}
}
//comparable 参数只有一个数
class m implements Comparable {
@Override
public int compareTo(Object o) {
return 0;
}
}
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
int[] vec = new int[k];
Arrays.sort(arr);
for (int i = 0; i < k; ++i) {
vec[i] = arr[i];
}
return vec;
}
}
剑指 Offer 41. 数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例 1:
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
示例 2:
输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]
思路:
双堆
大顶堆 保存较小的一半 堆顶为前一半最大值
小顶堆 保存较大的一半 堆顶为后一半最小值
偶数 (大顶堆堆顶+小顶堆堆顶)/2
奇数 大顶堆堆顶
bigHeap = new PriorityQueue<>(
(n1, n2) -> n2 - n1
);
smallHeap = new PriorityQueue<>(
(n1, n2) -> n1 - n2
);
class MedianFinder {
/** initialize your data structure here. */
/**
* 方法一:双堆
*/
PriorityQueue<Integer> smallHeap, bigHeap;
public MedianFinder() {
bigHeap = new PriorityQueue<>(
(n1, n2) -> n2 - n1
);
smallHeap = new PriorityQueue<>(
(n1, n2) -> n1 - n2
);
}
/* 始终保持大顶堆栈顶【小于】小顶堆栈顶 */
public void addNum(int num) {
//两者平衡 可能都是空 也可能元素个数相等
if(bigHeap.size()==smallHeap.size()){
//大顶堆为空 优先加入大顶堆
//两者不为空 根据num的值调整两堆
if(bigHeap.size()==0||smallHeap.peek()>num){
bigHeap.offer(num);
}else{
bigHeap.offer(smallHeap.poll());
smallHeap.offer(num);
}
}else{
//两堆大小不相等 根据num的值调整相等
if(bigHeap.peek()<num){
smallHeap.offer(num);
}else{
smallHeap.offer(bigHeap.poll());
bigHeap.offer(num);
}
}
}
public double findMedian() {
//两堆元素个数相等 偶数 取堆顶的平均数
//两堆个数不等 取大顶堆堆
return bigHeap.size()==smallHeap.size()? ((double)bigHeap.peek()+smallHeap.peek())/2:bigHeap.peek();
}
}
剑指 Offer 42. 连续子数组的最大和
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
提示:
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100
思路:
动态规划
遍历数组
如果前n个的和为负数,则从当前元素开始算起 ,不计算前面的了
返回最大值
class Solution {
public int maxSubArray(int[] nums) {
int[] dp=new int[nums.length];
dp[0]=nums[0];
int max=nums[0];
for(int i=1;i<nums.length;i++){
dp[i]=Math.max(nums[i]+dp[i-1],nums[i]);
max=Math.max(dp[i],max);
}
return max;
}
}
剑指 Offer 45. 把数组排成最小的数
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
示例 1:
输入: [10,2]
输出: "102"
示例 2:
输入: [3,30,34,5,9]
输出: "3033459"
提示:
0 < nums.length <= 100
说明:
输出结果可能非常大,所以你需要返回一个字符串而不是整数
拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0
思路:
利用Arrays.sort() 方法 重写Comparable接口
快速排序:
排序规则 a+b>b+a return b+a
a+b
class Solution {
public String minNumber(int[] nums) {
if(nums.length==0) return null;
if(nums.length==1) return String.valueOf(nums[0]);
Mycomparator c=new Mycomparator();
String[] res=new String[nums.length];
for(int i=0;i<nums.length;i++){
res[i]=String.valueOf(nums[i]);
}
Arrays.sort(res,c);
StringBuilder s=new StringBuilder();
for(String i:res){
s.append(i);
}
return s.toString();
}
}
class Mycomparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
if((o1+o2).compareTo(o2+o1)>0) return 1;
if((o1+o2).compareTo(o2+o1)<0) return -1;
else return 0;
}
}
剑指 Offer 53 - I. 在排序数组中查找数字 I
统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
限制:
0 <= 数组长度 <= 50000
//从头到尾遍历
class Solution {
public int search(int[] nums, int target) {
int res=0;
for(int i=0;i<nums.length;i++){
if(nums[i]==target){
res++;
}
}
return res;
}
}
//二分查找
class Solution {
public int search(int[] nums, int target) {
int left=0,right=nums.length-1,res=0;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]==target){
left=mid;
right=mid;
while(left>=0 && nums[left]==target){
left--;
}
while(right<nums.length && nums[right]==target){
right++;
}
return right-left-1;
}else if(nums[mid]<target){
left=mid+1;
}else if(nums[mid]>target){
right=mid-1;
}
}
return res;
}
}
剑指 Offer 53 - II. 0~n-1中缺失的数字
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
限制:
1 <= 数组长度 <= 10000
思路:
二分查找
class Solution {
public int missingNumber(int[] nums) {
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] == m) i = m + 1;
else j = m - 1;
}
return i;
}
}
剑指 Offer 56 - I. 数组中数字出现的次数
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
限制:
2 <= nums.length <= 10000
思路:
该思路 空间复杂度不行
用一个HashSet 判断set中是否含有当前元素
如果有 就移除
如果没有 就添加
最后set中的元素就是最后的答案
class Solution {
public int[] singleNumbers(int[] nums) {
HashSet<Integer> set=new HashSet<Integer>();
for(int i=0;i<nums.length;i++){
if(set.contains(nums[i])){
set.remove(nums[i]);
}else{
set.add(nums[i]);
}
}
int[] res=new int[2];
int index=0;
for(Integer i:set){
res[index]=i;
index++;
}
return res;
}
}
思路2 分组异或
两数相等异或结果为0,一个数与0异或结果就等于其本身。
所以如果数组中只有一个出现一次的数,那么就只需要对所有的数进行异或就可以得到这个只出现一次的数,而本题中出现一次的数有两个。所以所有数异或的结果就是那两个只出现一次的数异或的结果。所以根据这个特性,我们就可以采用分组的方法解决此问题。且分组要满足两个条件。
1、两个相同的数必须出现在同一组。
2、那两个只出现一次的数必须分配在不同的组。
这样我们分别对这两组数进行异或,就可以得到两个出现一次的数。那么,究竟应该怎么分组呢?
例如【4,1,4,6】:全部异或的结果就是1和6异或的结果。就是0001和0110异或的结果0111。其实我们不难发现。将该两个相同的数分配在一组是很容易实现的。我们只需要固定一个二进制位,若这两个数在这个二进制位上的数是相同的。我们就把他分在同一组。但是难点还是在如何实现将两个子出现一次的数分配在不同的组里面。
往下分析,1和6异或结果就是0111,0111这个二进制数中是1的二进制位暗含了什么个意思呢?分析不难知道,二进制位是1,就表示1和6在这个二进制位上的数是不同的。所以,这就是我们划分两个数到不同组的依据。因为0111有三个二进制位都是1,分别是第一位、第二位、第三位。这就表示了1和6的二进制数在第一、二、三位上的数是不同的。
我们假设就以第一个二进制位为划分标准。当数组中的数的第一个二进制位是1的就分为第一组。数组中的数第一个二进制位是0的就划分为第二组。这样就成功的将1和6分到了不同的组别中,而相同的数例如4,因为4和4的第一个二进制位是必然相等的,这样也就实现了将两个相同的数划分到同一组。最后只需要分别将这两个组进行异或,就可以得到我们要求的答案
class Solution {
public int[] singleNumbers(int[] nums) {
int ret = 0;
for(int num:nums){
ret ^= num;//ret最终答案就是那两个只出现一次的的数异或的结果
}
//找到ret二进制数中第几位是1
int target = 1;//初始位0001
while((target & ret)==0){
//如果target第一个二进制位不为1,就将target左移一位位0010,然后与相与,判断ret第二位是否为一.按此循环,知道找到ret的第一个为1的二进制位
target = target<<1;
}
int a = 0, b = 0;
for(int num:nums){
if((num & target)==0){
//第一组
a ^= num;
}else{
//第二组
b ^= num;
}
}
return new int[]{
a,b};
}
}
剑指 Offer 56 - II. 数组中数字出现的次数 II
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:
输入:nums = [3,4,3,3]
输出:4
示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
限制:
1 <= nums.length <= 10000
1 <= nums[i] < 2^31
class Solution {
public int singleNumber(int[] nums) {
HashMap<Integer,Integer> set=new HashMap<Integer,Integer>();
for(int i=0;i<nums.length;i++){
if(set.containsKey(nums[i])){
set.put(nums[i],set.get(nums[i])+1);
}else{
set.put(nums[i],1);
}
}
for(Map.Entry<Integer,Integer> entry:set.entrySet()){
if(entry.getValue()==1){
return entry.getKey();
}
}
return -1;
}
}
剑指 Offer 57. 和为s的两个数字
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:
输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]
限制:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^6
思路:
双指针 nums[left]+nums[right]==target
class Solution {
public int[] twoSum(int[] nums, int target) {
int left=0,right=nums.length-1;
while(left<right){
if(nums[left]>target) return null;
while(nums[right]>target){
right--;
}
if(nums[left]+nums[right]==target){
return new int[]{
nums[left],nums[right]};
}else if(nums[left]+nums[right]<target){
left++;
}else if(nums[left]+nums[right]>target){
right--;
}
}
return null;
}
}
剑指 Offer 61. 扑克牌中的顺子
从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
示例 1:
输入: [1,2,3,4,5]
输出: True
示例 2:
输入: [0,0,1,2,5]
输出: True
限制:
数组长度为 5
数组的数取值为 [0, 13] .
思路:
case1 数组大小为0 false
case2 从小到大排序 如果最后一个元素为0 则数组中所有元素为0 true
case3 数组中有除0之外的重复元素 false
case4 从小到大排序 第一个不是0的元素 和 最后一个元素之差 - 索引之差 > 0的个数 false
否则 return true
import java.util.Arrays;
import java.util.HashSet;
class Solution {
public boolean isStraight(int[] nums) {
if(nums.length==0) return false;
Arrays.sort(nums);
if(nums[nums.length-1]==0) return true;
HashSet<Integer> set=new HashSet<Integer>();
for(int i:nums){
if(i!=0){
if(set.contains(i)){
return false;
}else{
set.add(i);
}
}
}
int first_not_zero=0;
while(nums[first_not_zero]==0){
first_not_zero++;
}
if((nums[nums.length-1]-nums[first_not_zero])-(nums.length-1-first_not_zero)<=first_not_zero){
return true;
}
return false;
}
}
剑指 Offer 62. 圆圈中最后剩下的数字
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
示例 1:
输入: n = 5, m = 3
输出: 3
示例 2:
输入: n = 10, m = 17
输出: 2
限制:
1 <= n <= 10^5
1 <= m <= 10^6
思路:
用一个ArrayList模拟约瑟夫环
每次移除元素的位置 idx = (idx + m - 1) % n;
class Solution {
public int lastRemaining(int n, int m) {
ArrayList<Integer> list = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
list.add(i);
}
int idx = 0;
while (n > 1) {
idx = (idx + m - 1) % n;
list.remove(idx);
n--;
}
return list.get(0);
}
}
18. 四数之和
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:答案中不可以包含重复的四元组。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [], target = 0
输出:[]
提示:
0 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res=new ArrayList<>();
List<Integer> arr=new ArrayList<>();
Arrays.sort(nums);
int length=nums.length;
if(nums.length<4)return res;
for(int i=0;i<nums.length-3;i++){
//固定第一个数字
//nums[i-1]的结果 和 nums[i]的结果一样 直接排除
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
//最小的四个数加起来都比target大 直接结束循环 不可能有比target小的了
if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
break;
}
//最大的三个数和这个相加都小于target 直接排除 进入下一层循环
if (nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) {
continue;
}
//此处三个if语句可以把不满足的直接忽略,大大节省时间
int j=i+1;//固定第二个数字
//此刻变成三数之和 固定第二个数字 target=target-nums[i]
while(j<nums.length-2){
int start=j+1;
int end=nums.length-1;
while(start<end){
//此时问题已经和三数之和一样了
if(nums[i]+nums[j]+nums[start]+nums[end]==target){
arr.add(nums[i]);
arr.add(nums[j]);
arr.add(nums[start]);
arr.add(nums[end]);
res.add(new ArrayList<>(arr));
arr.clear();
while(start<end&&nums[start]==nums[start+1]){
start++;
}//此部分为start去重,一样的数字要跳过
while(start<end&&nums[end]==nums[end-1]){
end--;
}//此部分为end去重,一样的数字要跳过
start++;
end--;
}else if(nums[i]+nums[j]+nums[start]+nums[end]<target){
start++;
}else if(nums[i]+nums[j]+nums[start]+nums[end]>target){
end--;
}
}
while(j<nums.length-2&&nums[j]==nums[j+1]){
j++;
}//此部分为j部分去重,一样的数字要跳过
j++;
}
}
return res;
}
}
875. 爱吃香蕉的珂珂
珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。
珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。
珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。
示例 1:
输入: piles = [3,6,7,11], H = 8
输出: 4
示例 2:
输入: piles = [30,11,23,4,20], H = 5
输出: 30
示例 3:
输入: piles = [30,11,23,4,20], H = 6
输出: 23
提示:
1 <= piles.length <= 10^4
piles.length <= H <= 10^9
1 <= piles[i] <= 10^9
思路:
暴力解法:从速度最小值开始判断 不断递增 直到找到合适的解 O(N^2)
二分查找:速度最大最小的中间值依次查找 o(Nlog(N))
class Solution {
public int minEatingSpeed(int[] piles, int h) {
int minspeed=1;
int maxspeed=getMax(piles)+1;
while(minspeed<maxspeed){
int mid=minspeed+(maxspeed-minspeed)/2;
if(canfinish(mid,piles,h)){
maxspeed=mid;
}else{
minspeed=mid+1;
}
}
return minspeed;
}
public int getMax(int[] piles){
Arrays.sort(piles);
return piles[piles.length-1];
}
public boolean canfinish(int i,int[] piles,int h){
int time=0;
for(int temp:piles){
if(temp<=i){
time=time+1;
}else{
int a0=(int)(temp/i);
int a1=temp%i;
if(a1==0){
time=time+a0;
}else{
time=time+a0+1;
}
}
}
if(time<=h) return true;
return false;
}
}
1011. 在 D 天内送达包裹的能力
传送带上的包裹必须在 D 天内从一个港口运送到另一个港口。
传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
返回能在 D 天内将传送带上的所有包裹送达的船的最低运载能力。
示例 1:
输入:weights = [1,2,3,4,5,6,7,8,9,10], D = 5
输出:15
解释:
船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
第 1 天:1, 2, 3, 4, 5
第 2 天:6, 7
第 3 天:8
第 4 天:9
第 5 天:10
请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。
示例 2:
输入:weights = [3,2,2,4,1,4], D = 3
输出:6
解释:
船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示:
第 1 天:3, 2
第 2 天:2, 4
第 3 天:1, 4
示例 3:
输入:weights = [1,2,3,1,1], D = 4
输出:3
解释:
第 1 天:1
第 2 天:2
第 3 天:3
第 4 天:1, 1
提示:
1 <= D <= weights.length <= 5 * 104
1 <= weights[i] <= 500
class Solution {
public int shipWithinDays(int[] weights, int D) {
// 载重可能的最小值
int left = 0;
// 载重可能的最大值 + 1
int right = getSum(weights);
while (left < right) {
int mid = left + right >>1 ;
if (judge(weights, D, mid)) {
right = mid;
} else {
left = mid+1;
}
}
return right;
}
public boolean judge(int[] w,int D,int cap){
int i = 0;
for (int day = 0; day < D; day++) {
int maxCap = cap;
while ((maxCap -= w[i]) >= 0) {
i++;
if (i == w.length)
return true;
}
}
return false;
}
public int getMax(int[] weights){
Arrays.sort(weights);
return weights[weights.length-1];
}
}
380. 常数时间插入、删除和获取随机元素
设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构。
insert(val):当元素 val 不存在时,向集合中插入该项。
remove(val):元素 val 存在时,从集合中移除该项。
getRandom:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。
示例 :
// 初始化一个空的集合。
RandomizedSet randomSet = new RandomizedSet();
// 向集合中插入 1 。返回 true 表示 1 被成功地插入。
randomSet.insert(1);
// 返回 false ,表示集合中不存在 2 。
randomSet.remove(2);
// 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。
randomSet.insert(2);
// getRandom 应随机返回 1 或 2 。
randomSet.getRandom();
// 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。
randomSet.remove(1);
// 2 已在集合中,所以返回 false 。
randomSet.insert(2);
// 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。
randomSet.getRandom();
思路:
插入 删除 getRandom都是O(1)
hashMap 结合 ArrayList
hashMap中存储 元素的索引 value key
ArrayList 用于 获取随机的数
class RandomizedSet {
Map<Integer, Integer> dict;
List<Integer> list;
Random rand = new Random();
/** Initialize your data structure here. */
public RandomizedSet() {
dict = new HashMap();
list = new ArrayList();
}
/** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
public boolean insert(int val) {
if (dict.containsKey(val)) return false;
dict.put(val, list.size());
list.add(list.size(), val);
return true;
}
/** Removes a value from the set. Returns true if the set contained the specified element. */
public boolean remove(int val) {
if (! dict.containsKey(val)) return false;
// move the last element to the place idx of the element to delete
int lastElement = list.get(list.size() - 1);
int idx = dict.get(val);
list.set(idx, lastElement);
dict.put(lastElement, idx);
// delete the last element
list.remove(list.size() - 1);
dict.remove(val);
return true;
}
/** Get a random element from the set. */
public int getRandom() {
return list.get(rand.nextInt(list.size()));
}
}
/**
* Your RandomizedSet object will be instantiated and called as such:
* RandomizedSet obj = new RandomizedSet();
* boolean param_1 = obj.insert(val);
* boolean param_2 = obj.remove(val);
* int param_3 = obj.getRandom();
*/
710. 黑名单中的随机数
给定一个包含 [0,n ) 中独特的整数的黑名单 B,写一个函数从 [ 0,n ) 中返回一个不在 B 中的随机整数。
对它进行优化使其尽量少调用系统方法 Math.random() 。
提示:
1 <= N <= 1000000000
0 <= B.length < min(100000, N)
[0, N) 不包含 N,详细参见 interval notation 。
示例 1:
输入:
["Solution","pick","pick","pick"]
[[1,[]],[],[],[]]
输出: [null,0,0,0]
示例 2:
输入:
["Solution","pick","pick","pick"]
[[2,[]],[],[],[]]
输出: [null,1,1,1]
示例 3:
输入:
["Solution","pick","pick","pick"]
[[3,[1]],[],[],[]]
Output: [null,0,0,2]
示例 4:
输入:
["Solution","pick","pick","pick"]
[[4,[2]],[],[],[]]
输出: [null,1,3,1]
输入语法说明:
输入是两个列表:调用成员函数名和调用的参数。Solution的构造函数有两个参数,N 和黑名单 B。pick 没有参数,输入参数是一个列表,即使参数为空,也会输入一个 [] 空列表
//维护一个白名单
//通过用例 65/67 时间超时
class Solution {
HashSet<Integer> set=new HashSet<Integer>();
List<Integer> list=new ArrayList<Integer>();
Random r=new Random();
public Solution(int N, int[] blacklist) {
for(int i:blacklist){
set.add(i);
}
for(int i=0;i<N;i++){
if(!set.contains(i)){
list.add(i);
}
}
}
public int pick() {
return list.get(r.nextInt(list.size()));
}
}
/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(N, blacklist);
* int param_1 = obj.pick();
*/
// 黑名单映射
//思路:
// 通过random 返回一个白名单数值范围内的数 0-(n-b.length)
// 通过hashset函数 过滤掉wlen--n中的黑名单数据
// 此时hashset中剩余的就是0-wlen中黑名单数据对应的白名单索引
class Solution {
Map<Integer, Integer> m;
Random r;
int wlen;
public Solution(int n, int[] b) {
m = new HashMap<>();
r = new Random();
wlen = n - b.length;
Set<Integer> w = new HashSet<>();
for (int i = wlen; i < n; i++) w.add(i); //w的大小为b.length
for (int x : b) w.remove(x); //把w中的黑名单数移除, w中的白名单数量就是 wlen前面黑名单数的数量
Iterator<Integer> wi = w.iterator(); //创建w的迭代器
for (int x : b) //遍历黑名单
if (x < wlen) //把黑名单中小于wlen的数 指向白名单
m.put(x, wi.next());
}
public int pick() {
int k = r.nextInt(wlen);//获取一个随机数0-wlen
return m.getOrDefault(k, k);//如果m中不包含就返回自己 包含就返回对应的值
}
}
26. 删除有序数组中的重复项
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
示例 1:
输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 2:
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
提示:
0 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
nums 已按升序排列
思路:
快慢指针 慢指针用于修改数据
class Solution {
public int removeDuplicates(int[] nums) {
if (nums.length == 0) {
return 0;
}
int slow = 0, fast = 0;
while (fast < nums.length){
if(nums[fast]!=nums[slow]){
slow++;
nums[slow]=nums[fast];
}
fast++;
}
return slow+1;
}
}
83. 删除排序链表中的重复元素
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 。
返回同样按升序排列的结果链表。
示例 1:
输入:head = [1,1,2]
输出:[1,2]
示例 2:
输入:head = [1,1,2,3,3]
输出:[1,2,3]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y21K6tTJ-1633515954138)(D:\学习笔记\刷题笔记截图\list1.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MoTGvKPi-1633515954139)(D:\学习笔记\刷题笔记截图\list2.jpg)]
/**
* 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 deleteDuplicates(ListNode head) {
if (head == null) return null;
ListNode slow = head, fast = head;
while (fast != null) {
if (fast.val != slow.val) {
// nums[slow] = nums[fast];
slow.next = fast;
// slow++;
slow = slow.next;
}
// fast++
fast = fast.next;
}
// 断开与后面重复元素的连接
slow.next = null;
return head;
}
}
27. 移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
示例 1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
提示:
0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100
class Solution {
public int removeElement(int[] nums, int val) {
int fast = 0, slow = 0;
while (fast < nums.length) {
if (nums[fast] != val) {
nums[slow] = nums[fast];
slow++;
}
fast++;
}
return slow;
}
}
283. 移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数
class Solution {
public void moveZeroes(int[] nums) {
int n = nums.length, left = 0, right = 0;
while (right < n) {
if (nums[right] != 0) {
swap(nums, left, right);
left++;
}
right++;
}
}
public void swap(int[] nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
496. 下一个更大元素 I
给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
示例 1:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
示例 2:
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
提示:
1 <= nums1.length <= nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 104
nums1和nums2中所有整数 互不相同
nums1 中的所有整数同样出现在 nums2 中
思路:
hashmap 中保存nums2的 value key
遍历nums1
找到在nums2中的索引位置 从nums2的索引处往后面找大于这个数并赋值 没找到就是-1
依次遍历并且赋值
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
HashMap<Integer,Integer> map=new HashMap<Integer,Integer>();
for(int i=0;i<nums2.length;i++){
map.put(nums2[i],i);
}
for(int i=0;i<nums1.length;i++){
int index=map.get(nums1[i]);
nums1[i]=findBig(index,nums2);
}
return nums1;
}
public int findBig(int index,int[] nums2){
int o1=nums2[index];
for(int i=index+1;i<nums2.length;i++){
if(nums2[i]>o1) return nums2[i];
}
return -1;
}
}
674. 最长连续递增序列
给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。
示例 1:
输入:nums = [1,3,5,4,7]
输出:3
解释:最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。
示例 2:
输入:nums = [2,2,2,2,2]
输出:1
解释:最长连续递增序列是 [2], 长度为1。
提示:
1 <= nums.length <= 104
-109 <= nums[i] <= 109
class Solution {
public int findLengthOfLCIS(int[] nums) {
if(nums.length==0||nums.length==1) return nums.length;
int max=0;
int len=1;
for(int right=1;right<nums.length;right++){
if(nums[right]>nums[right-1]){
len++;
max=Math.max(len,max);
}else{
len=1;
}
}
return Math.max(len,max);
}
}
1312 给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。
请你返回让 s 成为回文串的 最少操作次数 。「回文串」是正读和反读都相同的字符串。
输入:s = "zzazz"
输出:0
解释:字符串 "zzazz" 已经是回文串了,所以不需要做任何插入操作。
输入:s = "mbadm"
输出:2
解释:字符串可变为 "mbdadbm" 或者 "mdbabdm"
动态规划:
先从长度为1的字符串开始计算最小插入次数
dp[i][j]=dp[i+1][j-1];
dp[i][j]=Math.min(dp[i+1][j],dp[i][j-1])+1;
axxxa左右都相等 dp[i][j]=dp[i+1][j-1];
axxxxb左右不相等 dp[i][j]=Math.min(dp[i+1][j],dp[i][j-1])+1;
axxxx 的最小次数 +1
xxxxb 的最小次数 +1
class Solution {
public int minInsertions(String s) {
int n=s.length();
int[][] dp=new int[n][n];
for(int i=n-2;i>=0;--i){
for(int j=i+1;j<n;j++){
if(s.charAt(i)==s.charAt(j)){
dp[i][j]=dp[i+1][j-1];
}else{
dp[i][j]=Math.min(dp[i+1][j],dp[i][j-1])+1;
}
}
}
return dp[0][n-1];
}
}
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
限制:
0 <= s 的长度 <= 10000
class Solution {
public String replaceSpace(String s) {
StringBuffer bf=new StringBuffer();
for(char c:s.toCharArray()){
if(c==' '){
bf.append("%20");
}else{
bf.append(c);
}
}
return bf.toString();
}
}
剑指 Offer 15.
请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例 2:
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例 3:
输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。
提示:
输入必须是长度为 32 的 二进制串
//字符串替换
public static int hammingWeight(int n) {
return Integer.toBinaryString(n).replaceAll("0","").length();
}
思路:
00000000000000000000000000001011 每次都和 00000000000000000000000000001 进行与运算 结果就是最后一位是不是1
0&1=0 1&1=1
然后无符号右移一位
直到n为0
//位运算
public class Solution {
public int hammingWeight(int n) {
int res = 0;
while(n != 0) {
res += n & 1;
n >>>= 1;
}
return res;
}
}
剑指 Offer 19. 正则表达式匹配
请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与"aa.a"和"ab*a"均不匹配。
示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
示例 4:
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'。
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length() + 1, n = p.length() + 1;
boolean[][] dp = new boolean[m][n];
//赋初始值 特殊情况
//s的前i个字符 p的前j个字符
dp[0][0] = true;
for(int j = 2; j < n; j += 2)
//举例:s="" p="a*a*a*" 则 s和p匹配
dp[0][j] = dp[0][j - 2] && p.charAt(j - 1) == '*';
//当p的最后一位是*时有两种情况
// 1 dp[i][j-2] 举例 s: aaabbb p: aaabbbc* 等价于 aaabbb 和aaabbb *代表0
// 2 dp[i-1][j]为true 同时 s的最后一个字符和p的倒数第2个字符相等 或者 p以.*结尾
//当p的最后一位不是*的情况时
// 1 dp[i - 1][j - 1]为true 同时s p最后结尾字符相等或者p以.结尾
for(int i = 1; i < m; i++) {
for(int j = 1; j < n; j++) {
dp[i][j] = p.charAt(j - 1) == '*' ?
dp[i][j - 2] || dp[i - 1][j] && (s.charAt(i - 1) == p.charAt(j - 2) || p.charAt(j - 2) == '.') :
dp[i - 1][j - 1] && (p.charAt(j - 1) == '.' || s.charAt(i - 1) == p.charAt(j - 1));
}
}
return dp[m - 1][n - 1];
}
}
剑指 Offer 20. 表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、"5e2"、"-123"、"3.1416"、"-1E-16"、"0123"都表示数值,但"12e"、"1a3.14"、"1.2.3"、"+-5"及"12e+5.4"都不是
思路:
设置三个符号位:
isnum 表示前一个字符是数字
isDot 表示前面是否出现过点
ise_or_E表示前面是否有e或E
判断规则:
遍历字符数组,
如果是数字0-9:就把isnum=true
如果是'.' :.前面不能是. e E,.前可以没有整数 isDot=true
如果是'e'或'E',前面必须isNum=true 或者 没有e 。 ise_or_E=true、isNum=faalse
如果是+ - ,+-只能出现在最开始 或者 e的后面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mwr1Lkam-1633515954140)(D:\学习笔记\刷题笔记截图\image-20210706202238202.png)]
class Solution {
public boolean isNumber(String s) {
if(s == null || s.length() == 0) return false; // s为空对象或 s长度为0(空字符串)时, 不能表示数值
boolean isNum = false, isDot = false, ise_or_E = false; // 标记是否遇到数位、小数点、‘e’或'E'
char[] str = s.trim().toCharArray(); // 删除字符串头尾的空格,转为字符数组,方便遍历判断每个字符
for(int i=0; i<str.length; i++) {
if(str[i] >= '0' && str[i] <= '9') isNum = true; // 判断当前字符是否为 0~9 的数位
else if(str[i] == '.') {
// 遇到小数点
//1. .1 1.1 都是数字
if(isDot || ise_or_E) return false; // 小数点之前可以没有整数,但是不能重复出现小数点、或出现‘e’、'E'
isDot = true; // 标记已经遇到小数点
}
else if(str[i] == 'e' || str[i] == 'E') {
// 遇到‘e’或'E'
if(!isNum || ise_or_E) return false; // ‘e’或'E'前面必须有整数,且前面不能重复出现‘e’或'E'
ise_or_E = true; // 标记已经遇到‘e’或'E'
isNum = false; // 重置isNum,因为‘e’或'E'之后也必须接上整数,防止出现 123e或者123e+的非法情况
}
else if(str[i] == '-' ||str[i] == '+') {
if(i!=0 && str[i-1] != 'e' && str[i-1] != 'E') return false; // 正负号只可能出现在第一个位置,或者出现在‘e’或'E'的后面一个位置
}
else return false; // 其它情况均为不合法字符
}
return isNum;
}
}
剑指 Offer 38. 字符串的排列
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]
限制:
1 <= s 的长度 <= 8
思路:
回溯算法 用一个boolean数组表示元素是否被访问过
//回溯算法模板
private void backtrack("原始参数") {
//终止条件(递归必须要有终止条件)
if ("终止条件") {
//一些逻辑操作(可有可无,视情况而定)
return;
}
for (int i = "for循环开始的参数"; i < "for循环结束的参数"; i++) {
//一些逻辑操作(可有可无,视情况而定)
//做出选择
//递归
backtrack("新的参数");
//一些逻辑操作(可有可无,视情况而定)
//撤销选择
}
}
class Solution {
public String[] permutation(String s) {
Set<String> res = new HashSet<>();
backtrack(s.toCharArray(), "", new boolean[s.length()], res);
return res.toArray(new String[res.size()]);
}
private void backtrack(char[] chars,String res,boolean[] b,Set set){
//边界条件判断,当选择的字符长度等于原字符串长度的时候,说明原字符串的字符都已经
//选完了
if(res.length()==chars.length){
set.add(res);
return;
}
//每一个节点我们都要从头开始选
for(int i=0;i<chars.length;i++){
//已经选择过的就不能再选了
if(b[i]==true){
continue;
}
b[i]=true;
//表示选择当前字符
res=res+chars[i];
//把当前字符选择后,到树的下一层继续选
backtrack(chars,res,b,set);
//递归往回走的时候要撤销选择
b[i]=false;
}
}
}
剑指 Offer 46. 把数字翻译成字符串
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
提示:
0 <= num < 231
思路:
回溯算法
遍历所有结果
class Solution {
public int translateNum(int num) {
String number = String.valueOf(num);
return translate(number, 0, 0);
}
private int translate(String number, int index, int count) {
if(index == number.length())
return count + 1;
char ch = number.charAt(index);
if(ch >= '0' && ch <= '9') {
count = translate(number, index + 1, count);
}
if(index < number.length() - 1) {
char ch2 = number.charAt(index + 1);
if(ch == '1' || (ch == '2' && ch2 <= '5' && ch2 >= '0')) {
count = translate(number, index + 2, count);
}
}
return count;
}
}
剑指 Offer 50. 第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例:
s = "abaccdeff"
返回 "b"
s = ""
返回 " "
限制:
0 <= s 的长度 <= 50000
思路:
遍历字符串,放入一个LinkedHashMap集合中,记录出现次数
遍历map集合 找出第一个出现次数为一的值
class Solution {
public char firstUniqChar(String s) {
if(s=="") return ' ';
Map<Character,Integer> set=new LinkedHashMap<Character,Integer>();
int index=0;
while(index<s.length()){
if(set.containsKey(s.charAt(index))){
set.put( s.charAt(index) , set.get(s.charAt(index)) + 1);
}else{
set.put(s.charAt(index),1);
}
index++;
}
if(set.size()==0) return ' ';
for(Map.Entry<Character,Integer> entry:set.entrySet()){
if(entry.getValue()==1){
return entry.getKey();
}
}
return ' ';
}
}
剑指 Offer 58 - I. 翻转单词顺序
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
示例 2:
输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
说明:
无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
思路:
分割字符串
倒序 拼接的在stringBuilder上
class Solution {
public String reverseWords(String s) {
String[] res=s.trim().split(" ");
StringBuilder b=new StringBuilder();
for(int i=res.length-1;i>=0;i--){
if(judge(res[i])){
b.append(res[i].trim());
if(i==0){
break;
}
b.append(" ");
}
}
return b.toString().trim();
}
public boolean judge(String s){
for(int i=0;i<s.length();i++){
if(s.charAt(i)!=' '){
return true;
}
}
return false;
}
}
思路:
双指针
1 先去除前后的空格
2 寻找第一个空格 跳过空格间隙
class Solution {
public String reverseWords(String s) {
s = s.trim(); // 删除首尾空格
int j = s.length() - 1, i = j;
StringBuilder res = new StringBuilder();
while(i >= 0) {
while(i >= 0 && s.charAt(i) != ' ') i--; // 搜索首个空格
res.append(s.substring(i + 1, j + 1) + " "); // 添加单词
while(i >= 0 && s.charAt(i) == ' ') i--; // 跳过单词间空格
j = i; // j 指向下个单词的尾字符
}
return res.toString().trim(); // 转化为字符串并返回
}
}
剑指 Offer 58 - II. 左旋转字符串
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
输入: s = "abcdefg", k = 2
输出: "cdefgab"
示例 2:
输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"
限制:
1 <= k < s.length <= 10000
思路:
元素的正确位置 (i+s.length()-n)%s.length()
class Solution {
public String reverseLeftWords(String s, int n) {
char[] chars = s.toCharArray();
char[] res=new char[chars.length];
for(int i=0;i<chars.length;i++){
res[(i+s.length()-n)%s.length()]=chars[i];
}
String r="";
for(int i=0;i<s.length();i++){
r=r+res[i];
}
return r;
}
}
剑指 Offer 67. 把字符串转换成整数
写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
示例 1:
输入: "42"
输出: 42
示例 2:
输入: " -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
示例 3:
输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
示例 4:
输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
因此无法执行有效的转换。
示例 5:
输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。
因此返回 INT_MIN (−231)
class Solution {
public static void main(String[] args) {
System.out.println(strToInt("-89h54fsd"));
}
public static int strToInt(String str) {
char[] c=str.toCharArray();
boolean sign_add=false;
boolean sign_sub=false;
boolean sign_num=false;
StringBuilder s=new StringBuilder();
for(char i:c){
if(i>='0'&&i<='9'){
if(s.length()==0||sign_num==true){
sign_num=true;
s.append(i);
}
} else if(i=='+'){
sign_add=true;
sign_num=false;
sign_sub=false;
} else if(i=='-'){
sign_sub=true;
sign_add=false;
sign_num=false;
} else if(i<'0'||i>'9'){
if(sign_num==true){
break;
}
sign_add=false;
sign_sub=false;
sign_num=false;
}
}
String res=s.toString();
if(res=="") return 0;
if(Double.valueOf(res)>=Integer.MAX_VALUE){
if(sign_add){
return Integer.MAX_VALUE;
}
if(sign_sub){
if(-Double.valueOf(res)<=Integer.MIN_VALUE){
return Integer.MIN_VALUE;
}
}
return Integer.MAX_VALUE;
}
if(sign_sub){
return -Integer.valueOf(res);
}
if(sign_add){
return Integer.valueOf(res);
}
return Integer.valueOf(res);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZbzfvxGM-1633515954140)(D:\学习笔记\刷题笔记截图\image-20210707080437330.png)]
思路:
1 去除空格 并转化为字符数组
2 第一个字符是否是 + -
3 是 + - 则从索引1处开始遍历 不是 + - 从索引0处开始遍历
4 如果发现不是数字 直接break
5 越界条件判断 目前的res > bndry || res == bndry && str.charAt(j) > '7' 则越界
6 否则不越界 res=res*10+chars[j]-'0';
7 return sign*res
class Solution {
public int strToInt(String str) {
int res = 0, bndry = Integer.MAX_VALUE / 10;
int i = 0, sign = 1, length = str.length();
if(length == 0) return 0;
while(str.charAt(i) == ' ')
if(++i == length) return 0;
if(str.charAt(i) == '-') sign = -1;
if(str.charAt(i) == '-' || str.charAt(i) == '+') i++;
for(int j = i; j < length; j++) {
if(str.charAt(j) < '0' || str.charAt(j) > '9') break;
if(res > bndry || res == bndry && str.charAt(j) > '7')
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
res = res * 10 + (str.charAt(j) - '0');
}
return sign * res;
}
}
14. 最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例 1:
输入:strs = ["flower","flow","flight"]
输出:"fl"
示例 2:
输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。
提示:
1 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i] 仅由小写英文字母组成
class Solution {
public String longestCommonPrefix(String[] strs) {
int minindex=0;
int minlength=Integer.MAX_VALUE;
for(int i=0;i<strs.length;i++){
if(strs[i].length()<minlength){
minlength=strs[i].length();
minindex=i;
}
}
for(int j=0;j<minlength;j++){
char temp=strs[0].charAt(j);
for(int i=0;i<strs.length;i++){
if(strs[i].charAt(j)!=temp){
return strs[i].substring(0,j);
}
}
}
return strs[minindex];
}
}
claas Solution{
public void buildMaxHeap(int[] a, int heapSize) {
//调整成顶堆形式
for (int i = heapSize/2 ; i >= 0; --i) {
maxHeapify(a, i, heapSize);
}
//堆调整
for(int j=a.length-1;j>=0;j--){
swap(a,0,j);
maxHeapify(a,0,j);
}
}
//大顶堆函数
public void maxHeapify(int[] a, int i, int heapSize) {
int l = i * 2 + 1, r = i * 2 + 2, largest = i;
if (l < heapSize && a[l] > a[largest]) largest = l;
if (r < heapSize && a[r] > a[largest]) largest = r;
if (largest != i) {
swap(a, i, largest);
maxHeapify(a, largest, heapSize);
}
}
//定义一个交换函数
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
思想:
冒泡排序的升级
先取一个中间值,用两个指针,依次向中间遍历
分别找到小于中间值的数和大于中间值的数,交换,把数组分成两个部分
再次对两个数组排序
依次进行
class QuickSort {
public void quicksort(int[] nums,int left,int right){
int povit=nums[(left+right)/2],l=left,r=right;
while(l<r){
while(nums[l]<povit) l++;
while(nums[r]>povit) r--;
if(l>=r) break;
swap(nums,r,l);
if(nums[r]==povit) l++;
if(nums[l]==povit) r--;
}
if(l==r){
l++;
r--;
}
if(l<right) quicksort(nums,l,right);
if(left<r) quicksort(nums,left, r);
}
public void swap(int[] nums,int i,int j){
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
}
思想:
假设第一个数是一个有序数组
遍历数组,通过比较插入数据和有序数组的大小,找到插入位置
赋值,完成排序
public void insertsort(int[] nums){
for(int i=1;i<nums.length;i++){
int temp=nums[i],index=0;
for(int j=i-1;j>=0;j--){
if(nums[j]<temp){
index=j+1;
break;
}
}
for(int j=i-1;j>=index;j--){
nums[j+1]=nums[j];
}
nums[index]=temp;
}
}
1 先分组,遍历
2 对每个组进行插入排序
public static void shellSort(int[] arr) {
int temp=0;
for(int gap=arr.length/2;gap>0;gap=gap/2){
for(int i=gap;i<arr.length;i++){
for(int j=i-gap;j>=0;j=j-gap){
if(arr[j]<arr[j+gap]){
temp=arr[j];
arr[j]=arr[j+gap];
arr[j+gap]=temp;
}
}
}
}
}
进行n-1次遍历
遍历的时候碰见逆序就交换
public static void bubbleSort(int[] arr) {
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
}
}
}
}
public static void swap(int[] num,int i,int j){
int temp=num[i];
num[i]=num[j];
num[j]=temp;
}
思路:
第一次 从arr[0]-arr[n-1]找到最小的数与arr[0]互换
第二次 从arr[1]-arr[n-1]找到最小的数与arr[1]互换
.....
public static void selectsort(int[] arr) {
for(int i=0;i<arr.length;i++){
int min=arr[i];
int minindex=i;
for(int j=i+1;j<arr.length;j++){
if(arr[j]<min){
min=arr[j];
minindex=j;
}
}
swap(arr,i,minindex);
}
}
public static void swap(int[] num,int i,int j){
int temp=num[i];
num[i]=num[j];
num[j]=temp;
}
思想
1 先进行数组拆分
2 再依次合并
第一个 分
1 从中间分开,左边右两边都进行分割
2 分割后进行合并 此处用了递归
第二个 合
1 用两个指针遍历两个数组,依次比较,按照顺序把元素放到temp数组中
2 把剩余的元素移动道temp数组中
3 把temp数组复制到原来的数组中
class Solution {
int res = 0;
public int InversePairs(int [] array) {
mergeSort(array,0,array.length-1);
return res;
}
public void mergeSort(int[] array,int start,int end){
if(start >= end) return;
int mid = start + (end-start)/2;
mergeSort(array,start,mid);
mergeSort(array,mid+1,end);
merge(array,start,mid,end);
}
public void merge(int[] array,int start,int mid,int end){
int[] tempArray = new int[end-start+1];
int i = start,j = mid+1,k = 0;
while(i <= mid && j <= end){
if(array[i] <= array[j]){
tempArray[k++] = array[i++];
}else{
tempArray[k++] = array[j++];
res = (res + mid-i+1) % 1000000007;
}
}
while(i <= mid) tempArray[k++] = array[i++];
while(j <= end) tempArray[k++] = array[j++];
for(k = 0;k < tempArray.length;k++){
array[start+k] = tempArray[k];
}
}
}
//基数排序方法
public static void radixSort(int[] arr) {
//根据前面的推导过程,我们可以得到最终的基数排序代码
//1. 得到数组中最大的数的位数
int max = arr[0]; //假设第一数就是最大数
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//得到最大数是几位数
int maxLength = (max + "").length();
//定义一个二维数组,表示 10 个桶, 每个桶就是一个一维数组
//说明
//1. 二维数组包含 10 个一维数组
//2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为 arr.length
//3. 名明确,基数排序是使用空间换时间的经典算法
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
//可以这里理解
//比如:bucketElementCounts[0] , 记录的就是 bucket[0] 桶的放入数据个数
int[] bucketElementCounts = new int[10];
//这里我们使用循环将代码处理
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
//(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位..
for (int j = 0; j < arr.length; j++) {
//取出每个元素的对应位的值
int digitOfElement = arr[j] / n % 10;
//放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
//遍历每一桶,并将桶中是数据,放入到原数组
for (int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中,有数据,我们才放入到原数组
if (bucketElementCounts[k] != 0) {
//循环该桶即第 k 个桶(即第 k 个一维数组), 放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放入到 arr
arr[index++] = bucket[k][l];
}
}
//第 i+1 轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
bucketElementCounts[k] = 0;
}
}
}
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
限制:
0 <= 节点个数 <= 5000
map保存中序
声明前序为全局变量
recur函数(前序根节点索引,中序树的做边界,中序树的右边界)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
HashMap<Integer, Integer> map = new HashMap<>();//标记中序遍历
int[] preorder;//保留的先序遍历,方便递归时依据索引查看先序遍历的值
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder;
//将中序遍历的值及索引放在map中,方便递归时获取左子树与右子树的数量及其根的索引
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
//三个索引分别为
//当前根的的索引
//递归树的左边界,即数组左边界
//递归树的右边界,即数组右边界
return recur(0,0,inorder.length-1);
}
/**
* @param pre_root 先序遍历的根节点的索引
* @param in_left 中序遍历的中这棵树的左侧索引边界
* @param in_right 中序遍历的右侧这棵树的索引边界
*/
TreeNode recur(int pre_root, int in_left, int in_right){
if(in_left > in_right) return null;// 相等的话就是自己
TreeNode root = new TreeNode(preorder[pre_root]);//获取root节点
int idx = map.get(preorder[pre_root]);//获取在中序遍历中根节点所在索引,以方便获取左子树的数量
//左子树的根的索引为先序中的根节点+1
//递归左子树的左边界为原来的中序in_left
//递归右子树的右边界为中序中的根节点索引-1
root.left = recur(pre_root+1, in_left, idx-1);
//右子树的根的索引为先序中的 当前根位置 + 左子树的数量 + 1
//递归右子树的左边界为中序中当前根节点+1
//递归右子树的有边界为中序中原来右子树的边界
root.right = recur(pre_root + (idx - in_left) + 1, idx+1, in_right);
return root;
}
}
剑指 Offer 26. 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/ \
4 5
/ \
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
示例 1:
输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:
输入:A = [3,4,5,1,2], B = [4,1]
输出:true
限制:
0 <= 节点个数 <= 10000
思路:
用递归 判断A树的子树是否和B树相等
recur方法:用于判断A树是否和B树相等
isSubStructure方法:用于遍历A的子树 判断和B是否相等
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
//该方法 判断 a的左右子树是否和b相等,依次往下遍历
public boolean isSubStructure(TreeNode A, TreeNode B) {
return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B));
}
//这个只是判断a树b树是否相等
boolean recur(TreeNode A, TreeNode B) {
if(B == null) return true;
if(A == null || A.val != B.val) return false;
return recur(A.left, B.left) && recur(A.right, B.right);
}
}
剑指 Offer 27. 二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
4
/ \
2 7
/ \ / \
1 3 6 9
镜像输出:
4
/ \
7 2
/ \ / \
9 6 3 1
示例 1
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
限制:
0 <= 节点个数 <= 1000
思路:
递归
先定义一个 交换左右子树的函数
在从根节点开始 依次调用该函数
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root==null) return root;
recur(root);
if(root.left!=null) mirrorTree(root.left);
if(root.right!=null) mirrorTree(root.right);
return root;
}
//交换左右子树
public void recur(TreeNode r){
TreeNode temp=r.left;
r.left=r.right;
r.right=temp;
}
}
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root==null) return null;
TreeNode left=root.left;
TreeNode right=root.right;
root.left=mirrorTree(right);
root.right=mirrorTree(left);
return root;
}
}
剑指 Offer 28. 对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
限制:
0 <= 节点个数 <= 1000
思路:
递归:
先构造一个判断左右子树是否相等的函数
依次从根节点往下遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
return root == null ? true : recur(root.left, root.right);
}
boolean recur(TreeNode L, TreeNode R) {
if(L == null && R == null) return true;
if(L == null || R == null || L.val != R.val) return false;
return recur(L.left, R.right) && recur(L.right, R.left);
}
}
剑指 Offer 32 - I. 从上到下打印二叉树
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回:
[3,9,20,15,7]
提示:
节点总数 <= 1000
思路:
广度优先
使用单链表作为辅助,用广度优先 队列中 保存了每一层的结点
class Solution {
public int[] levelOrder(TreeNode root) {
if (root == null) // 空树则返回空数组
return new int[0];
Queue<TreeNode> q = new LinkedList<> (); // 借助一个队列,通过 BFS 实现按层遍历二叉树
ArrayList<Integer> tmp =new ArrayList<> (); // 申请一个动态数组 ArrayList 动态添加节点值
q.offer(root); // 根结点先入队
while (q.size() != 0) {
TreeNode node = q.poll(); // 取出当前队首元素
tmp.add(node.val);
if(node.left != null) q.offer(node.left); // 左子节点入队
if(node.right != null) q.offer(node.right); // 右子节点入队
}
// 将 ArrayList 转为 int数组并返回
int[] res = new int[tmp.size()];
for (int i=0; i<res.length; i++) {
res[i] = tmp.get(i);
}
return res;
}
}
剑指 Offer 32 - II. 从上到下打印二叉树 II
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
提示:
节点总数 <= 1000
思路:
在while循环里添加一个for循环 循环次数就是 每层的长度
1 root > 1
2 root.left root.right >2
...
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
if(root != null) queue.add(root);
while(!queue.isEmpty()) {
List<Integer> tmp = new ArrayList<>();
for(int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
tmp.add(node.val);
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(tmp);
}
return res;
}
}
剑指 Offer 32 - III. 从上到下打印二叉树 III
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[20,9],
[15,7]
]
提示:
节点总数 <= 1000
思路:
在第二个题的基础上,增加层数判断 res.size()%==0 奇数层从左到右添加 偶数层从右向左添加
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
if(root != null) queue.add(root);
while(!queue.isEmpty()) {
LinkedList<Integer> tmp = new LinkedList<>();
for(int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
if(res.size() % 2 == 0) tmp.addLast(node.val); // 偶数层 -> 队列头部
else tmp.addFirst(node.val); // 奇数层 -> 队列尾部
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(tmp);
}
return res;
}
}
剑指 Offer 33. 二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
参考以下这颗二叉搜索树:
5
/ \
2 6
/ \
1 3
示例 1:
输入: [1,6,3,2,5]
输出: false
示例 2:
输入: [1,3,2,6,5]
输出: true
提示:
数组长度 <= 1000
思路:
对于二叉排序树 如果是中序的话 只需要判断是否是有序即可
本题是后序输出 左--右--根
用递归的思想:
最后一个元素是根节点 从头开始遍历 第一个比根节点大的数就是右子树 可以将数组分为 左子树 右子树 根
在分别对左子树、右子树 进行判断
判断规则:
左子树在遍历时就确定比根结点小,需要遍历右子树看是否比根节点大,如果有小于根节点的就返回false
class Solution {
public boolean verifyPostorder(int[] postorder) {
return helper(postorder, 0, postorder.length - 1);
}
boolean helper(int[] postorder, int left, int right) {
//如果left==right,就一个节点不需要判断了,如果left>right说明没有节点,
//也不用再看了,否则就要继续往下判断
if (left >= right)
return true;
//因为数组中最后一个值postorder[right]是根节点,这里从左往右找出第一个比
//根节点大的值,他后面的都是根节点的右子节点(包含当前值,不包含最后一个值,
//因为最后一个是根节点),他前面的都是根节点的左子节点
int mid = left;
int root = postorder[right];
while (postorder[mid] < root)
mid++;
int temp = mid;
//因为postorder[mid]前面的值都是比根节点root小的,
//我们还需要确定postorder[mid]后面的值都要比根节点root大,
//如果后面有比根节点小的直接返回false
while (temp < right) {
if (postorder[temp++] < root)
return false;
}
//然后对左右子节点进行递归调用
return helper(postorder, left, mid - 1) && helper(postorder, mid, right - 1);
}
}
剑指 Offer 34. 二叉树中和为某一值的路径
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例:
给定如下二叉树,以及目标和 target = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
提示:
节点总数 <= 10000
class Solution {
LinkedList<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
recur(root, sum);
return res;
}
void recur(TreeNode root, int tar) {
if(root == null) return;
path.add(root.val);
tar -= root.val;
if(tar == 0 && root.left == null && root.right == null)
res.add(new LinkedList(path));
recur(root.left, tar);
recur(root.right, tar);
path.removeLast();
}
}
思路:
深度优先 没往下走一次就计算一个total值 到根节点 比较total和sum 相等就添加到结果中 不相等就返回
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<List<Integer>> result = new ArrayList<>();
dfs(root, sum, 0, new ArrayList<>(), result);
return result;
}
public void dfs(TreeNode root, int sum, int toal, List<Integer> list,
List<List<Integer>> result) {
//如果节点为空直接返回
if (root == null)
return;
//把当前节点值加入到list中
list.add(new Integer(root.val));
//没往下走一步就要计算走过的路径和
toal += root.val;
//如果到达叶子节点,就不能往下走了,直接return
if (root.left == null && root.right == null) {
//如果到达叶子节点,并且sum等于toal,说明我们找到了一组,
//要把它放到result中
if (sum == toal)
result.add(new ArrayList(list));
//注意别忘了把最后加入的结点值给移除掉,因为下一步直接return了,
//不会再走最后一行的remove了,所以这里在rerurn之前提前把最后
//一个结点的值给remove掉。
list.remove(list.size() - 1);
//到叶子节点之后直接返回,因为在往下就走不动了
return;
}
//如果没到达叶子节点,就继续从他的左右两个子节点往下找
dfs(root.left, sum, toal, list, result);
dfs(root.right, sum, toal, list, result);
//我们要理解递归的本质,当递归往下传递的时候他最后还是会往回走,
//我们把这个值使用完之后还要把它给移除,这就是回溯
list.remove(list.size() - 1);
}
}
剑指 Offer 36. 二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针
思路:
二叉搜索树 的中序遍历就是从小到大排序
因此 按照中序遍历的顺序修改指针 left表示前驱指针 right表示后继指针
class Solution {
Node pre,head;
public Node treeToDoublyList(Node root) {
if(root==null) return null;
inorder(root);
head.left=pre;
pre.right=head;
return head;
}
public void inorder(Node node){
if(node==null) return;
inorder(node.left);
if(pre!=null){
pre.right=node;
}else{
head=node;
}
node.left=pre;
pre=node;
inorder(node.right);
}
}
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val,Node _left,Node _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
class Solution {
private Node head = null;
private Node pre = null;
public Node treeToDoublyList(Node root) {
if (root == null)
return root;
inorder(root);
head.left = pre;
pre.right = head;
return head;
}
//二叉树的中序遍历
private void inorder(Node root) {
if (root == null)
return;
inorder(root.left);
//下面是对当前节点的操作
if (pre == null) {
head = root;
} else {
pre.right = root;
}
root.left = pre;
pre = root;
inorder(root.right);
}
}
剑指 Offer 37. 序列化二叉树
请实现两个函数,分别用来序列化和反序列化二叉树。
示例:
你可以将以下二叉树:
1
/ \
2 3
/ \
4 5
序列化为 "[1,2,3,null,null,4,5]"
序列化:
广度优先 如果root==null 依然放进去
反序列化:
广度优先 利用队列新建二叉树
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if(root == null){
return "";
}
StringBuilder res = new StringBuilder();
res.append("[");
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
if(node != null){
res.append("" + node.val);
queue.offer(node.left);
queue.offer(node.right);
}else{
res.append("null");
}
res.append(",");
}
res.append("]");
return res.toString();
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if(data == ""){
return null;
}
String[] dataList = data.substring(1, data.length() - 1).split(",");
TreeNode root = new TreeNode(Integer.parseInt(dataList[0]));
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int i = 1;
while(!queue.isEmpty()){
TreeNode node = queue.poll();
if(!"null".equals(dataList[i])){
node.left = new TreeNode(Integer.parseInt(dataList[i]));
queue.offer(node.left);
}
i++;
if(!"null".equals(dataList[i])){
node.right = new TreeNode(Integer.parseInt(dataList[i]));
queue.offer(node.right);
}
i++;
}
return root;
}
}
剑指 Offer 54. 二叉搜索树的第k大节点
给定一棵二叉搜索树,请找出其中第k大的节点。
示例 1:
输入: root = [3,1,4,null,2], k = 1
3
/ \
1 4
\
2
输出: 4
示例 2:
输入: root = [5,3,6,2,4,null,null,1], k = 3
5
/ \
3 6
/ \
2 4
/
1
输出: 4
限制:
1 ≤ k ≤ 二叉搜索树元素个数
思路:
中序遍历 放到一个ArrayList中 返回索引在 arr.size()-k 处的元素
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int kthLargest(TreeNode root, int k) {
List<Integer> list=new ArrayList<>();
dfs(root,list);
return list.get(list.size()-k);
}
void dfs(TreeNode root,List list) {
if(root==null) return;
dfs(root.left,list);
list.add(root.val);
dfs(root.right,list);
}
}
剑指 Offer 55 - I. 二叉树的深度
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
例如:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
提示:
节点总数 <= 10000
思路:
广度优先
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if(root==null) return 0;
Queue<TreeNode> q=new LinkedList<>();
q.offer(root);
int deapth=0;
while(!q.isEmpty()){
for(int i=q.size();i>0;i--){
TreeNode temp=q.poll();
if(temp.left!=null) q.offer(temp.left);
if(temp.right!=null) q.offer(temp.right);
}
deapth++;
}
return deapth;
}
}
剑指 Offer 55 - II. 平衡二叉树
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true 。
示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1
/ \
2 2
/ \
3 3
/ \
4 4
返回 false 。
限制:
0 <= 树的结点个数 <= 10000
思路:
利用Ⅰ的结论
按照中序遍历 依次判断每个结点是否是非平衡的树
如果有不平衡的 修改 flag 并直接返回
import java.util.LinkedList;
import java.util.Queue;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x; }
}
class Solution {
boolean flag=true;
public boolean isBalanced(TreeNode root) {
recur(root);
return flag;
}
public void recur(TreeNode node){
if(node==null) return;
recur(node.left);
if(maxDepth(node.left)-maxDepth(node.right)>1 || maxDepth(node.left)-maxDepth(node.right)<-1){
flag=false;
}
recur(node.right);
}
public int maxDepth(TreeNode root) {
if(root==null) return 0;
Queue<TreeNode> q=new LinkedList<>();
q.offer(root);
int deapth=0;
while(!q.isEmpty()){
for(int i=q.size();i>0;i--){
TreeNode temp=q.poll();
if(temp.left!=null) q.offer(temp.left);
if(temp.right!=null) q.offer(temp.right);
}
deapth++;
}
return deapth;
}
}
剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉搜索树中。
思路:
此处利用搜索二叉树的性质,如果某个结点的值位于这两个值中间,那么该结点就是最近的父节点
如果 root.val > p.val root.val > q.val root 左移
如果 root.val < p.val root.val < q.val root 右移
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null) return null;
if(root.val>p.val && root.val>q.val){
return lowestCommonAncestor(root.left,p,q);
}else if(root.val<p.val && root.val<q.val){
return lowestCommonAncestor(root.right,p,q);
}else if((root.val<=p.val && root.val>=q.val)||(root.val>=p.val && root.val<=q.val)){
return root;
}
return null;
}
}
剑指 Offer 68 - II. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中
思路:
前序遍历 +回溯
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null||root==p||root==q) return root;
TreeNode left=lowestCommonAncestor(root.left,p,q);
TreeNode right=lowestCommonAncestor(root.right,p,q);
if(left==null&&right==null) return null;
if(left==null) return right;
if(right==null) return left;
return root;
}
}
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
/*
如果 当前根为null,或者 当前根为任意目标节点,
递归结束,开始回溯
*/
if (root == null || root == p || root == q) {
return root;
}
/*
分别向 左子树 和 右子树 查询两个目标节点是否存在
*/
TreeNode leftRoot = lowestCommonAncestor(root.left, p, q);
TreeNode rightRoot = lowestCommonAncestor(root.right, p, q);
/*
若 都没有查到,则表示当前树中,不存在目标节点
*/
if (leftRoot == null && rightRoot == null) {
return null;
}
/*
若 其中一棵子树没有查到,则公共祖先为 另一棵子树
*/
if (leftRoot == null) {
return rightRoot;
}
if (rightRoot == null) {
return leftRoot;
}
/*
若 都查询到了,则目标节点在当前root两侧,
即:当前root为 最近公共祖先
*/
return root;
}
}
226. 翻转二叉树
翻转一棵二叉树。
示例:
输入:
4
/ \
2 7
/ \ / \
1 3 6 9
输出:
4
/ \
7 2
/ \ / \
9 6 3 1
备注:
这个问题是受到 Max Howell 的 原问题 启发的 :
谷歌:我们90%的工程师使用您编写的软件(Homebrew),但是您却无法在面试时在白板上写出翻转二叉树这道题,这太糟糕了
/**
* 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 invertTree(TreeNode root) {
if(root==null) return root;
recur(root);
return root;
}
public void recur(TreeNode root){
if(root!=null){
TreeNode temp=root.left;
root.left=root.right;
root.right=temp;
}
if(root.left!=null) recur(root.left);
if(root.right!=null) recur(root.right);
}
}
114. 二叉树展开为链表
给你二叉树的根结点 root ,请你将它展开为一个单链表:
展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。
示例 1:
输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [0]
输出:[0]
提示:
树中结点数在范围 [0, 2000] 内
-100 <= Node.val <= 100
/**
* 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 void flatten(TreeNode root) {
if(root==null) return;
ArrayList<TreeNode> list=new ArrayList<TreeNode>();
recur(root,list);
// 依次修改每个结点的指针
for(int i=1;i<list.size();i++){
TreeNode temp=list.get(i-1);
temp.left=null;
temp.right=list.get(i);
}
}
//前序遍历 放进ArrayList中
public void recur(TreeNode root,ArrayList<TreeNode> list){
//终止条件
if(root!=null) list.add(root);
if(root.left!=null) recur(root.left,list);
if(root.right!=null) recur(root.right,list);
}
}
116. 填充每个节点的下一个右侧节点指针
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
进阶:
你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
示例:
输入:root = [1,2,3,4,5,6,7]
输出:[1,#,2,3,#,4,5,6,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,'#' 标志着每一层的结束。
提示:
树中节点的数量少于 4096
-1000 <= node.val <= 1000
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
class Solution {
public Node connect(Node root) {
if(root==null || (root.left==null && root.right==null)) return root;
Queue<Node> q=new LinkedList<Node>();
ArrayList<ArrayList<Node>> res=new ArrayList<ArrayList<Node>>();
q.offer(root);
while(q.size()!=0){
ArrayList<Node> temp=new ArrayList<Node>();
for(int i=q.size();i>0;i--){
Node t=q.poll();
temp.add(t);
if(t.left!=null){
q.offer(t.left);
q.offer(t.right);
}
}
res.add(temp);
}
for(int i=0;i<res.size();i++){
for(int j=1;j<res.get(i).size();j++){
res.get(i).get(j-1).next=res.get(i).get(j);
res.get(i).get(j).next=null;
}
}
return root;
}
}
class Solution {
public Node connect(Node root) {
dfs(root);
return root;
}
void dfs(Node root) {
if(root==null) {
return;
}
Node left = root.left;
Node right = root.right;
//配合动画演示理解这段,以root为起点,将整个纵深这段串联起来
while(left!=null) {
left.next = right;
left = left.right;
right = right.left;
}
//递归的调用左右节点,完成同样的纵深串联
dfs(root.left);
dfs(root.right);
}
}
654. 最大二叉树
给定一个不含重复元素的整数数组 nums 。一个以此数组直接递归构建的 最大二叉树 定义如下:
二叉树的根是数组 nums 中的最大元素。
左子树是通过数组中 最大值左边部分 递归构造出的最大二叉树。
右子树是通过数组中 最大值右边部分 递归构造出的最大二叉树。
返回有给定数组 nums 构建的 最大二叉树 。
示例 1:
输入:nums = [3,2,1,6,0,5]
输出:[6,3,5,null,2,0,null,null,1]
解释:递归调用如下所示:
- [3,2,1,6,0,5] 中的最大值是 6 ,左边部分是 [3,2,1] ,右边部分是 [0,5] 。
- [3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。
- 空数组,无子节点。
- [2,1] 中的最大值是 2 ,左边部分是 [] ,右边部分是 [1] 。
- 空数组,无子节点。
- 只有一个元素,所以子节点是一个值为 1 的节点。
- [0,5] 中的最大值是 5 ,左边部分是 [0] ,右边部分是 [] 。
- 只有一个元素,所以子节点是一个值为 0 的节点。
- 空数组,无子节点。
1 <= nums.length <= 1000
0 <= nums[i] <= 1000
nums 中的所有整数 互不相同
//思路 递归
import java.util.Arrays;
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 constructMaximumBinaryTree(int[] nums) {
return recur(nums);
}
public TreeNode recur(int[] nums){
//终止条件
if(nums.length==0) return null;
if(nums.length==1){
return new TreeNode(nums[0]);
}
//返回最大值 及其索引
int max=nums[0];
int max_index=0;
for(int i=0;i<nums.length;i++){
if(nums[i]>max){
max=nums[i];
max_index=i;
}
}
//将数组分为两部分 左半边部分 右半边部分
TreeNode root=new TreeNode(max);
int[] left= Arrays.copyOfRange(nums,0,max_index);
int[] right=Arrays.copyOfRange(nums,max_index+1,nums.length);
//再左右分别递归 返回一个根节点
//修改本结点的左右结点
root.left=recur(left);
root.right=recur(right);
return root;
}
}
105. 从前序与中序遍历序列构造二叉树
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
思路:递归
前序遍历=[[根节点][左子树][右子树]]
中序遍历=[[左子树][根节点][右子树]]
把中序遍历的数组放到hashmap中 方便查找前序遍历的根节点的索引
然后划分出 左子树的范围 右子树的范围
方法参数(前序遍历数组,中序遍历数组,左子树在前序遍历中的范围,左子树在中序遍历中的范围)
class Solution {
private Map<Integer, Integer> indexMap;
public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
//终止条件 前序遍历的范围<0
if (preorder_left > preorder_right) {
return null;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点
int inorder_root = indexMap.get(preorder[preorder_root]);
// 先把根节点建立出来
TreeNode root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
//方法参数(前序遍历数组,中序遍历数组,左子树在前序遍历中的范围,左子树在中序遍历中的范围)
root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
// 构造哈希映射,帮助我们快速定位根节点
indexMap = new HashMap<Integer, Integer>();
for (int i = 0; i < n; i++) {
indexMap.put(inorder[i], i);
}
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
}
106. 从中序与后序遍历序列构造二叉树
根据一棵树的中序遍历与后序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
/**
* 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 {
HashMap<Integer, Integer> map=null;
public TreeNode buildTree(int[] inorder, int[] postorder) {
map = new HashMap<Integer,Integer>();
int n=inorder.length;
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i],i);
}
return recur(inorder,postorder,0,n-1,0,n-1);
}
public TreeNode recur(int[] inorder, int[] postorder, int post_left, int post_right,int in_left, int in_right){
if(post_left>post_right){
return null;
}
//根据post_right找到根节点的值
int val=postorder[post_right];
//根据根节点的值找到根节点在中序遍历数组中的位置
int index=map.get(val);
//及左子树长度
int left_length=index-in_left;
TreeNode root=new TreeNode(val);
root.left=recur(inorder,postorder,post_left,post_left+left_length-1,in_left,index-1);
root.right=recur(inorder,postorder,post_left+left_length,post_right-1,index+1,in_right-1);
return root;
}
}
652. 寻找重复的子树
给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。
两棵树重复是指它们具有相同的结构以及相同的结点值。
示例 1:
1
/ \
2 3
/ / \
4 2 4
/
4
下面是两个重复的子树:
2
/
4
和
4
因此,你需要以列表的形式返回上述重复子树的根结点
//若传入的key对应的value为null或者key不存在Map中,则返回defaultValue
default V getOrDefault(Object key, V defaultValue)
/**
* 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 {
//存储后序遍历的字符串 ,value值 为1
HashMap<String, Integer> memo = new HashMap<>();
//用于存放结果
LinkedList<TreeNode> list = new LinkedList<>();
//
public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
traverse(root);
return list;
}
public String traverse(TreeNode root){
//中止条件
if(root == null){
return "#";
}
//后续遍历 生成该节点的遍历字符串
String left = traverse(root.left);
String right = traverse(root.right);
String result = left + ","+right+","+root.val;
//是否包含 包含就返回flag的值 不包含就返回默认值0
int flag = memo.getOrDefault(result,0);
//如果包含 且出现过一次,就把该节点放在结果List中
//如果出现多次则不用放在list中
//falg为>1则不用再次添加
if(flag == 1){
list.add(root);
}
//将flag加1 如果flag原来为0 则变为1 原来为1 则变为2
memo.put(result,flag+1);
//返回结果集
return result;
}
}
230. 二叉搜索树中第K小的元素
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
示例 1:
输入:root = [3,1,4,null,2], k = 1
输出:1
示例 2:
输入:root = [5,3,6,2,4,null,null,1], k = 3
输出:3
提示:
树中的节点数为 n 。
1 <= k <= n <= 104
0 <= Node.val <= 104
/**
* 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;
* }
* }
*/
//根据二叉搜索树的性质 按照中序遍历顺序 第k个元素就是要找的值
class Solution {
ArrayList<Integer> list=new ArrayList<Integer>();
public int kthSmallest(TreeNode root, int k) {
if(root==null) return -1;
recur(root, k);
if(k>list.size()){
return -1;
}else{
return list.get(k-1);
}
}
public void recur(TreeNode root, int k){
if(root==null || list.size()==k){
return;
}
if(root.left!=null){
recur(root.left,k);
}
list.add(root.val);
if(root.right!=null){
recur(root.right, k);
}
}
}
538. 把二叉搜索树转换为累加树
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树
示例 1:
输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
示例 2:
输入:root = [0,null,1]
输出:[1,null,1]
示例 3:
输入:root = [1,0,2]
输出:[3,3,2]
示例 4:
输入:root = [3,2,4,1]
输出:[7,9,4,10]
/**
* 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 {
int sum = 0;
public TreeNode convertBST(TreeNode root) {
if (root == null) {
return null;
}
convertBST(root.right);
sum += root.val;
root.val = sum;
convertBST(root.left);
return root;
}
}
450. 删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
说明: 要求算法时间复杂度为 O(h),h 为树的高度。
示例:
root = [5,3,6,2,4,null,7]
key = 3
5
/ \
3 6
/ \ \
2 4 7
给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
5
/ \
4 6
/ \
2 7
另一个正确答案是 [5,2,6,null,4,null,7]。
5
/ \
2 6
\ \
4 7
根据二叉搜索树的性质
如果目标节点大于当前节点值,则去右子树中删除;
如果目标节点小于当前节点值,则去左子树中删除;
如果目标节点就是当前节点,分为以下三种情况:
左右子节点都没有,删除该结点
其无左子:其右子顶替其位置,删除了该节点;
其无右子:其左子顶替其位置,删除了该节点;
其左右子节点都有:其左子树转移到其右子树的最左节点的左子树上,然后右子树顶替其位置,由此删除了该节点。
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if(root==null) return null;
if(key>root.val) root.right=deleteNode(root.right,key);
if(key<root.val) root.left=deleteNode(root.left,key);
if(key==root.val){
//1 无左右子树
if(root.left==null&&root.right==null){
return null;
}
//2 只有左子树
if(root.left!=null&&root.right==null){
return root.left;
}
//3 只有右子树
if(root.right!=null&&root.left==null){
return root.right;
}
//4 左右子树都有
if(root.left!=null && root.right!=null){
//寻找右子树最小值 替换该结点
TreeNode minNode=getMin(root.right);
root.val=minNode.val;
root.right = deleteNode(root.right, minNode.val);
}
}
return root;
}
TreeNode getMin(TreeNode node) {
// BST 最左边的就是最小的
while (node.left != null) node = node.left;
return node;
}
}
面试题 04.05. 合法二叉搜索树
实现一个函数,检查一棵二叉树是否为二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4
思路:
带参数递归
递归参数(要判断的结点,最小值,最大值)
对于右子树 node.right,本结点的值,upper
对于左子树 node.left,lower,本结点的值
class Solution {
public boolean isValidBST(TreeNode root) {
return judge(root,null,null);
}
/* 限定以 root 为根的子树节点必须满足 upper > root.val > lower */
public boolean judge(TreeNode node, Integer lower, Integer upper) {
// 基础解
if(node==null) return true;
// 若 root.val 不符合 max 和 min 的限制,说明不是合法 BST
int nodeval=node.val;
if(lower!=null&&nodeval<=lower){
return false;
}
if(upper!=null&&nodeval>=upper){
return false;
}
// 限定左子树的最大值是 root.val,右子树的最小值是 root.val
if(!judge(node.left,lower,nodeval)){
return false;
}
if(!judge(node.right,nodeval,upper)){
return false;
}
return true;
}
}
public class Solution{
public TreeNode insert(TreeNode node,TreeNode root){
if(root==null){
return node;
}
// if (root.val == val)
// BST 中一般不会插入已存在元素
if(node.val<root.val) root.left=insert(TreeNode node,root.left);
if(node.val>root.val) root.right=insert(TreeNode node,root.right);
}
return root;
}
222. 完全二叉树的节点个数
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
示例 1:
输入:root = [1,2,3,4,5,6]
输出:6
示例 2:
输入:root = []
输出:0
示例 3:
输入:root = [1]
输出:1
提示:
树中节点的数目范围是[0, 5 * 104]
0 <= Node.val <= 5 * 104
题目数据保证输入的树是 完全二叉树
/**
* 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 int countNodes(TreeNode root) {
TreeNode l=root,r=root;
int lh=0,rh=0;
while(l!=null){
l=l.left;
lh++;
}
while(r!=null){
r=r.right;
rh++;
}
if(lh==rh){
return (int)Math.pow(2,lh)-1;
}
return 1+countNodes(root.left)+countNodes(root.right);
}
}
341. 扁平化嵌套列表迭代器
给你一个嵌套的整型列表。请你设计一个迭代器,使其能够遍历这个整型列表中的所有整数。
列表中的每一项或者为一个整数,或者是另一个列表。其中列表的元素也可能是整数或是其他列表。
示例 1:
输入: [[1,1],2,[1,1]]
输出: [1,1,2,1,1]
解释: 通过重复调用 next 直到 hasNext 返回 false,next 返回的元素的顺序应该是: [1,1,2,1,1]。
示例 2:
输入: [1,[4,[6]]]
输出: [1,4,6]
解释: 通过重复调用 next 直到 hasNext 返回 false,next 返回的元素的顺序应该是: [1,4,6]
/**
* // This is the interface that allows for creating nested lists.
* // You should not implement it, or speculate about its implementation
* public interface NestedInteger {
*
* // @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();
*
* // @return the nested list that this NestedInteger holds, if it holds a nested list
* // Return empty list if this NestedInteger holds a single integer
* public List getList();
* }
*/
class NestedIterator implements Iterator<Integer> {
private Iterator<Integer> it;
public NestedIterator(List<NestedInteger> nestedList) {
// 存放将 nestedList 打平的结果
List<Integer> result = new LinkedList<>();
for (NestedInteger node : nestedList) {
// 以每个节点为根遍历
traverse(node, result);
}
// 得到 result 列表的迭代器
this.it = result.iterator();
}
public Integer next() {
return it.next();
}
public boolean hasNext() {
return it.hasNext();
}
// 遍历以 root 为根的多叉树,将叶子节点的值加入 result 列表
private void traverse(NestedInteger root, List<Integer> result) {
if (root.isInteger()) {
// 到达叶子节点
result.add(root.getInteger());
return;
}
// 遍历框架
for (NestedInteger child : root.getList()) {
traverse(child, result);
}
}
}
/**
* Your NestedIterator object will be instantiated and called as such:
* NestedIterator i = new NestedIterator(nestedList);
* while (i.hasNext()) v[f()] = i.next();
*/
剑指 Offer 06. 从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
限制:
0 <= 链表长度 <= 10000
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
List<Integer> list=new ArrayList<Integer>();
ListNode temp=new ListNode();
temp=head;
if(head!=null){
list.add(temp.val);
while(temp.next!=null){
list.add(temp.next.val);
temp=temp.next;
}
}
int[] result=new int[list.size()];
for(int i=list.size()-1,j=0;i>=0;i--,j++){
result[j]=list.get(i);
}
return result;
}
}
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 1:
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:
输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
提示:
1 <= values <= 10000
最多会对 appendTail、deleteHead 进行 10000 次调用
class CQueue {
LinkedList<Integer> A, B;
public CQueue() {
A = new LinkedList<Integer>();
B = new LinkedList<Integer>();
}
public void appendTail(int value) {
A.addLast(value);
}
public int deleteHead() {
if(!B.isEmpty()) return B.removeLast();
if(A.isEmpty()) return -1;
while(!A.isEmpty())
B.addLast(A.removeLast());
return B.removeLast();
}
}
public class Offer09 {
Stack<Integer> stack1,stack2;
public Offer09() {
stack1 = new Stack<>(); // 插入
stack2 = new Stack<>(); // 删除
}
public void appendTail(int value) {
stack1.push(value);
}
public int deleteHead() {
if (stack2.isEmpty()) {
// 删除栈B为空,则去A栈找数据
if (stack1.isEmpty()) {
// 若A栈也为空,那么无数据可删,则返回-1
return -1;
}
// A栈不为空,先将A栈的值压入B栈,再删除
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
剑指 Offer 18. 删除链表的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动
示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
思路:
从头结点开始依次遍历查找,直到找到val值的节点的父节点 让父节点指向val的下一个节点
if head==null return null
if head.val==val return head.next
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head==null) return null; //head 为null 直接返回
if(head.val==val) return head.next; //head的值就是要找的值 直接返回
ListNode temp=head; //创建辅助结点 指向头结点
while(temp.next.val!=val&&temp.next!=null){
//
temp=temp.next;
}
temp.next=temp.next.next;
return head;
}
}
剑指 Offer 22. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
思路:
常规:遍历链表 找出链表长度 返回第n-k个节点
双指针:一个前驱指针 一个后驱指针 前驱后驱之间相差k个节点 前驱遍历完 后驱结点就是要返回的结点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
if(head==null) return null;
ListNode first=head;
ListNode last=head;
int i=0;
while(first.next!=null){
first=first.next;
if(i<k){
i++;
}
if(i==k){
last=last.next;
}
}
return last;
}
}
剑指 Offer 24. 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
限制:
0 <= 节点个数 <= 5000
思路:
双指针
假设链表为1→2→3→∅,我们想要把它改成∅←1←2←3。
在遍历链表时,将当前节点的next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode first=head;
ListNode post=null;
while(first!=null){
ListNode temp=first.next;
first.next=post;
post=first;
first=temp;
}
return post;
}
}
剑指 Offer 25. 合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:
0 <= 链表长度 <= 1000
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dum = new ListNode(0), cur = dum;
while(l1 != null && l2 != null) {
if(l1.val < l2.val) {
cur.next = l1;
l1 = l1.next;
}
else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 != null ? l1 : l2;
return dum.next;
}
}
剑指 Offer 30. 包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.
提示:
各函数的调用总次数不超过 20000 次
思路:
四个方法 压入 弹出 获取栈顶 获取最小值
难点在最小值
初始化两个栈,一个用于当作栈使用 一个用来维护最小元素
push:A add B需要判断x是否小于最小值 如果是 ,就放进去 如果不是 就不用放
pop:A pop A弹出后 B要与A保持一致,判断A弹出的数是不是B的栈顶,是的话b也弹出
top:A peek
min:B peek
class MinStack {
Stack<Integer> A, B;
public MinStack() {
A = new Stack<>();
B = new Stack<>();
}
public void push(int x) {
A.add(x);
if(B.empty() || B.peek() >= x)
B.add(x);
}
public void pop() {
if(A.pop().equals(B.peek()))
B.pop();
}
public int top() {
return A.peek();
}
public int min() {
return B.peek();
}
}
剑指 Offer 31. 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {
1,2,3,4,5} 是某栈的压栈序列,序列 {
4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {
4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
提示:
0 <= pushed.length == popped.length <= 1000
0 <= pushed[i], popped[i] < 1000
pushed 是 popped 的排列
思路:
模拟栈的压入弹出
1.定义一个栈stack作为模拟栈
2.遍历pushed数组的同时将pushed中的元素压入stack模拟压栈过程
3.判断stack的栈顶元素是否与popped数组中的第i个元素相同,相同则弹出stack
4.最后判断stack是否弹空,弹空则返回true,不空则为false
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack = new Stack<>();
int i = 0;
for(int num : pushed) {
stack.push(num);
while(!stack.isEmpty() && stack.peek() == popped[i]) {
stack.pop();
i++;
}
}
return stack.isEmpty();
}
}
剑指 Offer 35. 复杂链表的复制
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
示例 4:
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
提示:
-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。
节点数目不超过 1000 。
思路:
链表的深拷贝问题
用Map集合 key保存原链表结点 value保存复制链表结点
第一遍先复制结点的值 next 和random指针先不复制
第二遍复制next 和 random指针
class Solution {
public Node copyRandomList(Node head) {
if(head==null) return head;
Map<Node,Node> map=new HashMap<Node,Node>();
for(Node i=head;i!=null;i=i.next){
map.put(i,new Node(i.val));
}
for(Node j=head;j!=null;j=j.next){
map.get(j).next=map.get(j.next);
map.get(j).random=map.get(j.random);
}
return map.get(head);
}
}
public Node copyRandomList(Node head) {
//边界条件判断
if (head == null)
return null;
//map存放已经创建的节点
Map<Node, Node> map = new HashMap<>();
Node cur = head;
//遍历链表的所有节点,创建一个和链表节点value值一样的
//新节点,这里的新节点的next和random都是空
while (cur != null) {
map.put(cur, new Node(cur.val));
cur = cur.next;
}
cur = head;
while (cur != null) {
//然后对新节点的next和random进行赋值
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
return map.get(head);
}
剑指 Offer 52. 两个链表的第一个公共节点
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode A = headA, B = headB;
while (A != B) {
A = A != null ? A.next : headB;
B = B != null ? B.next : headA;
}
return A;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA==null || headB==null) return null;
HashSet<ListNode> set=new HashSet<ListNode>();
for(ListNode i=headA;i!=null;i=i.next){
set.add(i);
}
for(ListNode j=headB;j!=null;j=j.next){
if(set.contains(j)){
return j;
}
}
return null;
}
}
剑指 Offer 59 - II. 队列的最大值
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
示例 1:
输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:
输入:
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
限制:
1 <= push_back,pop_front,max_value的总操作数 <= 10000
1 <= value <= 10^5
思路:
一个单链表
一个双端队列
class MaxQueue {
Queue<Integer> q;
Deque<Integer> d;
public MaxQueue() {
q = new LinkedList<Integer>();
d = new LinkedList<Integer>();
}
public int max_value() {
if (d.isEmpty()) {
return -1;
}
return d.peekFirst();
}
public void push_back(int value) {
while (!d.isEmpty() && d.peekLast() < value) {
d.pollLast();
}
d.offerLast(value);
q.offer(value);
}
public int pop_front() {
if (q.isEmpty()) {
return -1;
}
int ans = q.poll();
if (ans == d.peekFirst()) {
d.pollFirst();
}
return ans;
}
}
/**
* Your MaxQueue object will be instantiated and called as such:
* MaxQueue obj = new MaxQueue();
* int param_1 = obj.max_value();
* obj.push_back(value);
* int param_3 = obj.pop_front();
*/
206. 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
/**
* 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) return null;
if(head.next==null) return head;
ListNode pre=head.next;
ListNode post=head;
post.next=null;
while(pre!=null){
ListNode temp=pre.next;
pre.next=post;
post=pre;
pre=temp;
}
return post;
}
}
92. 反转链表 II
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
示例 2:
输入:head = [5], left = 1, right = 1
输出:[5]
提示:
链表中节点数目为 n
1 <= n <= 500
-500 <= Node.val <= 500
1 <= left <= right <= n
//利用反转链表I的结论 反转子链表 把要反转的链表 截断出来 进行反转
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
// 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode pre = dummyNode;
// 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
// 建议写在 for 循环里,语义清晰
for (int i = 0; i < left - 1; i++) {
pre = pre.next;
}
// 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
ListNode rightNode = pre;
for (int i = 0; i < right - left + 1; i++) {
rightNode = rightNode.next;
}
// 第 3 步:切断出一个子链表(截取链表)
ListNode leftNode = pre.next;
ListNode curr = rightNode.next;
// 注意:切断链接
pre.next = null;
rightNode.next = null;
// 第 4 步:同第 206 题,反转链表的子区间
reverseLinkedList(leftNode);
// 第 5 步:接回到原来的链表中
pre.next = rightNode;
leftNode.next = curr;
return dummyNode.next;
}
private void reverseLinkedList(ListNode head) {
// 也可以使用递归反转一个链表
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
}
}
234. 回文链表
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
//思路1 保存为数组 那样时间复杂度O(n) 空间复杂度O(n)
//思路2 快慢指针 慢指针走1步 快指针走两步 找到前半段链表的最后一个结点
// 反转后半段链表
// 比较两段链表
// 还原链表
// 时间复杂度O(n) 空间复杂度O(1)
/**
* 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 boolean isPalindrome(ListNode head) {
if (head == null) {
return true;
}
// 找到前半部分链表的尾节点并反转后半部分链表
ListNode firstHalfEnd = endOfFirstHalf(head);
ListNode secondHalfStart = reverseList(firstHalfEnd.next);
// 判断是否回文
ListNode p1 = head;
ListNode p2 = secondHalfStart;
boolean result = true;
while (result && p2 != null) {
if (p1.val != p2.val) {
result = false;
}
p1 = p1.next;
p2 = p2.next;
}
// 还原链表并返回结果
firstHalfEnd.next = reverseList(secondHalfStart);
return result;
}
//反转链表
private ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
//存储下一个结点
//修改本结点的next指针
//前后指针前移
while (curr != null) {
ListNode nextTemp = curr.next;
curr.next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
//快慢指针
//快指针每次走两步 慢指针每次走一步
//最后的结果
// 1 快指针后面还有一个结点 偶数个结点
// 2 快指针后面没有结点 奇数个结点
// 不管哪种情况 后半部分都是长度最小的那个链表
private ListNode endOfFirstHalf(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
}
//思路:
// 快慢指针 如果两个指针相遇 则一定有环
public boolean judge(Node node){
Node slow,fast;
slow=fast=node;
while(fast.next!=null||fast!=null){
if(fast==slow){
return true;
}
fast=fast.next.next;
slow=slow.next;
}
return false;
}
两节点相遇的位置 fast走过的路程一定是slow的二倍
这时 如果把slow放在头结点位置 slow和fast速度一致 那么也能在相遇点相遇 但是这时是第二次相遇
第一次相遇就是 环开始的地方
public boolean judge(Node node){
Node slow,fast;
slow=fast=node;
while(fast.next!=null||fast!=null){
if(fast==slow) break;
fast=fast.next.next;
slow=slow.next;
}
slow=node;
while(slow!=fast){
slow=slow.next;
fast=fast.next;
}
return slow;
}
思路:
快慢指针
public Node findMidNode(Node head){
if(head.next==null) return head;
Node slow=head.next;
Node fast=head.next.next;
while(fast.next!=null||fast.next.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
思路:
快慢指针 fast比slow快k-1步
public Node findMidNode(Node head,int k){
if(head.next==null) return head;
Node slow=head;
Node fast=head;
while(k-1!=0){
fast=fast.next;
k--;
}
while(fast!=null){
slow=slow.next;
fast=fast.next;
}
return slow;
}
23. 合并K个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i] 按 升序 排列
lists[i].length 的总和不超过 10^4
思路:
用一个优点队列 判断新链表怎么合并
import java.util.Comparator;
import java.util.HashMap;
import java.util.PriorityQueue;
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
int k = lists.length;
PriorityQueue<ListNode> q = new PriorityQueue<ListNode>(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val-o2.val;
}
});
for(int i=0;i<k;i++){
if(lists[i]!=null) q.offer(lists[i]);
}
//新构造的链表
ListNode head_ori=new ListNode();
ListNode head=head_ori;
//优先队列中的元素移除后 移动哪一根指针
while(q.peek()!=null){
//弹出一个最小值
ListNode tmp=q.poll();
//接到新链表尾部 head后移
head.next=tmp;
head=head.next;
//再往队列中添加一个元素
if(tmp.next!=null) q.add(tmp.next);
}
return head_ori.next;
}
}
class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
496. 下一个更大元素 I
给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
示例 1:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
示例 2:
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
提示:
1 <= nums1.length <= nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 104
nums1和nums2中所有整数 互不相同
nums1 中的所有整数同样出现在 nums2 中
思路:单调栈
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
HashMap<Integer,Integer> map = new HashMap<>();
Deque<Integer> stack = new LinkedList<>();
for(int i=0;i<nums2.length;i++){
while(!stack.isEmpty()&&stack.peek()<nums2[i]){
map.put(stack.pop(),nums2[i]);
}
stack.push(nums2[i]);
}
int[] ans=new int[nums1.length];
for(int i=0;i<nums1.length;i++){
ans[i]=map.getOrDefault(nums1[i],-1);
}
return ans;
}
}
503. 下一个更大元素 II
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
示例 1:
输入: [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
注意: 输入数组的长度不会超过 10000
思路:
单调栈
注意数组中的元素可以重复 因此栈中保存为数组的索引 map中保存 index value
因为环形数组 所以把数组长度扩大二倍 i%nums.length()
class Solution {
public int[] nextGreaterElements(int[] nums) {
HashMap<Integer,Integer> map=new HashMap<Integer,Integer>();
Deque<Integer> stack = new LinkedList<>();
for(int i=0;i<nums.length*2;i++){
while(!stack.isEmpty()&&nums[stack.peek()]<nums[i%nums.length]){
map.put(stack.pop(),nums[i%nums.length]);
}
stack.push(i%nums.length);
}
int[] ans=new int[nums.length];
for(int i=0;i<nums.length;i++){
ans[i]=map.getOrDefault(i,-1);
}
return ans;
}
}
232. 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
进阶:
你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。
示例:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
1 <= x <= 9
最多调用 100 次 push、pop、peek 和 empty
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)
class MyQueue {
Stack<Integer> enter,out;
/** Initialize your data structure here. */
public MyQueue() {
enter=new Stack<Integer>();
out=new Stack<Integer>();
}
/** Push element x to the back of queue. */
public void push(int x) {
enter.add(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
if(out.isEmpty()){
while(!enter.isEmpty()){
out.add(enter.pop());
}
}
return out.pop();
}
/** Get the front element. */
public int peek() {
if(out.isEmpty()){
while(!enter.isEmpty()){
out.add(enter.pop());
}
}
return out.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
if(enter.isEmpty()&&out.isEmpty()) return true;
return false;
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
225. 用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通队列的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
注意:
你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
提示:
1 <= x <= 9
最多调用100 次 push、pop、top 和 empty
每次调用 pop 和 top 都保证栈不为空
思路:
push: 每次push 都把刚push的值设为栈顶元素
top:直接返回栈顶元素
empty:return q.isEmpty()
pop: 先弹出前面的额n-2个元素加到队尾
把倒数第二个元素设置为栈顶 同时倒数第二个元素弹出添加到队尾
把最后一个元素弹出
import java.util.LinkedList;
import java.util.Queue;
class MyStack {
Queue<Integer> q;
int top;
/** Initialize your data structure here. */
public MyStack() {
q=new LinkedList<Integer>();
}
/** Push element x onto stack. */
public void push(int x) {
q.offer(x);
top=x;
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
int size=q.size();
while(size>2){
q.offer(q.poll());
size--;
}
top=q.peek();
q.offer(q.poll());
return q.poll();
}
/** Get the top element. */
public int top() {
return top;
}
/** Returns whether the stack is empty. */
public boolean empty() {
return q.isEmpty();
}
}
/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:1
示例 2:
输入:n = 5
输出:5
提示:
0 <= n <= 10
//递归法
class Solution {
public int fib(int n) {
if(n<=2){
if(n==2){
return 1;
}else if(n==1){
return 1;
}else if(n==0){
return 0;
}
}
return fib(n-1)+fib(n-2);
}
}
//记忆递归法
//动态规划
class Solution {
public int fib(int n) {
int a = 0, b = 1, sum;
for(int i = 0; i < n; i++){
sum = (a + b) % 1000000007;
a = b;
b = sum;
}
return a;
}
}
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21
示例 3:
输入:n = 0
输出:1
提示:
0 <= n <= 100
//和斐波那契数列很类似
class Solution {
public int numWays(int n) {
int a = 1, b = 1, sum;
for(int i = 0; i < n; i++){
sum = (a + b) % 1000000007;
a = b;
b = sum;
}
return a;
}
}
难度中等212
给你一根长度为 `n` 的绳子,请把绳子剪成整数长度的 `m` 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 `k[0],k[1]...k[m-1]` 。请问 `k[0]*k[1]*...*k[m-1]` 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 58
//余数可能为1 也可能为2
//为1 则n刚好是3的倍数加1 10/3=3...1
//为2 则刚好是3的倍数减1 8/3=2...2
class Solution {
public int cuttingRope(int n) {
if(n <= 3) return n - 1;
int a = n / 3, b = n % 3;
if(b == 0) return (int)Math.pow(3, a);
if(b == 1) return (int)Math.pow(3, a - 1) * 4;
return (int)Math.pow(3, a) * 2;
}
}
难度中等212
给你一根长度为 `n` 的绳子,请把绳子剪成整数长度的 `m` 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 `k[0],k[1]...k[m-1]` 。请问 `k[0]*k[1]*...*k[m-1]` 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18.答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 1000
思路:
与Ⅰ类似
不过这里要注意大数求余,int 32位 存储不下 需要不断求余
(xy)%p=[(x%p)(y%p)]%p
把绳子每3个长度分一段 ,让最后 剩下 2 3 4
while(n>4){
n=n-3;
}
class Solution {
public int cuttingRope(int n) {
if(n < 4){
return n - 1;
}
long res = 1;
while(n > 4){
res = res * 3 % 1000000007;
n -= 3;
}
return (int) (res * n % 1000000007);
}
}
剑指 Offer 16. 数值的整数次方
实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。
示例 1:
输入:x = 2.00000, n = 10
输出:1024.00000
示例 2:
输入:x = 2.10000, n = 3
输出:9.26100
示例 3:
输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25
提示:
-100.0 < x < 100.0
-231 <= n <= 231-1
-104 <= xn <= 104
思路:
1需要注意Integer.MIN_VALUE越界问题
2for遍历求 时间复杂度为O(n)
3用二分法求出结果 时间复杂度为O(log(n))
class Solution {
public double myPow(double x, int n) {
if(x == 0) return 0;
long b = n;//Integer.MIN_VALUE 变成正的 会越界 int是一个有符号数 所以用long来接受
double res = 1.0; //定义初始结果为1.0
if(b < 0) {
//进行判断 如果b为负数 就转化为 1/x -b 的问题
x = 1 / x;
b = -b;
}
//这里使用二分法 b为偶数 x^b b为奇数 (x^2)^(b-1)/2 * x
while(b > 0) {
if((b & 1) == 1) res *= x; //用&运算判断b是否为奇数
x *= x;
b >>= 1; //b除2
}
return res;
}
}
剑指 Offer 17. 打印从1到最大的n位数
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
示例 1
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
说明:
用返回一个整数列表来代替打印
n 为正整数
思路:
根据n 用stringbuffer拼接出n个9 再转化成int
for循环打印
class Solution {
public int[] printNumbers(int n) {
StringBuilder s=new StringBuilder();
for(int i=0;i<n;i++){
s.append(9);
}
int num=Integer.parseInt(s.toString());
int[] arr=new int[num];
for(int i=0;i<num;i++){
arr[i]=i+1;
}
return arr;
}
}
剑指 Offer 44. 数字序列中某一位的数字
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
示例 1:
输入:n = 3
输出:3
示例 2:
输入:n = 11
输出:0
限制:
0 <= n < 2^31
class Solution {
public int findNthDigit(int n) {
int digit = 1;
long start = 1;
long count = 9;
while (n > count) {
// 1.
n -= count;
digit += 1;
start *= 10;
count = digit * start * 9;
}
long num = start + (n - 1) / digit; // 2.
return Long.toString(num).charAt((n - 1) % digit) - '0'; // 3.
}
}
结果保留2位有效数字,精确到0.01%
class Solution {
public static void main(String[] args) {
System.out.println(sqrt(343));
}
public static String sqrt(double n){
int intl=1,intr=(int)n;
while(intl<=intr){
int intmid=(intl+intr)/2;
int intsum=intmid*intmid*intmid;
if(intsum==n){
return String.format("%.2f", (double)intmid);
}else if(intsum>n){
intr=intmid-1;
}else{
intl=intmid+1;
}
}
double l=1,r=n;
while(l<=r){
double mid=(l+r)/2.0;
double sum=mid*mid*mid;
double per=Math.abs(sum-n);
if((per/n)<=0.0001){
return String.format("%.2f", mid);
}else if(sum>n){
r= mid-0.0001;
}else if(sum<n){
l= mid+0.0001;
}
}
return "-1";
}
}
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)
[["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false
提示:
1 <= board.length <= 200
1 <= board[i].length <= 200
class Solution {
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
for(int i = 0; i < board.length; i++) {
for(int j = 0; j < board[0].length; j++) {
if(dfs(board, words, i, j, 0)) return true;
}
}
return false;
}
boolean dfs(char[][] board, char[] word, int i, int j, int k) {
if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;
if(k == word.length - 1) return true;
board[i][j] = '\0'; //防止回溯回去
boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) ||
dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
board[i][j] = word[k];
return res;
}
}
130. 被围绕的区域
给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。
示例 1:
输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
示例 2:
输入:board = [["X"]]
输出:[["X"]]
提示:
m == board.length
n == board[i].length
1 <= m, n <= 200
board[i][j] 为 'X' 或 'O'
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-InrCJBB2-1633515954165)(D:\学习笔记\刷题笔记截图\xogrid.jpg)]
class Solution {
int length;
int high;
public void solve(char[][] board) {
//遍历四条边
length=board.length;
high=board[0].length;
System.out.println(length+" "+high);
for(int i=0;i<high;i++){
//第一行
if(board[0][