目录
数据结构入门第一天
第一题 存在重复的元素(217,简单)
给定一个整数数组,判断是否存在重复元素。
如果存在一值在数组中出现至少两次,函数返回 true
。如果数组中每个元素都不相同,则返回 false
。
public class ContainsDuplicate {
/**
* 第一种方法 采用最传统的暴力循环,但是会超时
* public void ContainsDuplicate(int[] nums){
* //这里先给数组排序一下,这样判断相邻的元素是否有重复的
* Array.sort(sums);
* int n = nums.length;
* for(int i = 0; i < n - 1; i ++){
* if(sum[i] == sum[i + 1]){
* return true;
* }
* }
* return false;
* }
*
* 第二种
* public boolean containsDuplicate(int[] nums) {
* return IntStream.of(nums).distinct().count() != nums.length;
* }
* 使用了 Stream中的distinct方法,这个用来去除重复数,去重以后自然长度跟原数组长度不一致了。但是执行时间跟内存消耗不理想
*/
public static void main(String[] args) {
int[] sum = {1,5,4,78,59,23,59};
System.out.println(hashlala(sum));;
}
public static boolean hashlala(int[] sums){
Set<Integer> set = new HashSet<Integer>();
for(int x : sums){
if(!set.add(x)){
//System.out.println("有呢");
return true;
}
}
// System.out.println("没呢");
return false;
}
}
第二题最大子序和(53.简单)
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [0]
输出:0
// 动态规划
class Solution {
public int maxSubAray(int[] nums) {
int pre = 0, maxAns = nums[0];
for (int x : nums) {
//pre来维护对于当前f(i)的f(i−1)的值是多少
pre = Math.max(pre + x, x);//判断f(i-1)是否要加到当前数上
maxAns = Math.max(maxAns, pre);//获取最大值
}
return maxAns;
}
}
// 贪心法
class Solution{
public int maxSubAray(int[] nums){
//类似寻找最大最小值的题目,初始值一定要定义成理论上的最小最大值
int result = Integer.MIN_VALUE;
int numsSize = nums.length;
int sum = 0;
for (int i = 0; i < numsSize; i++){
sum += nums[i];
result = Math.max(result, sum);
//如果sum < 0,重新开始找子序串
if (sum < 0){
sum = 0;
}
}
return result;
}
}
// 分治法: 线段树
class Solution {
public int maxSubAray(int[] nums) {
if (nums == null || nums.length <= 0)// 输入校验
return 0;
int len = nums.length;// 获取输入长度
return getInfo(nums, 0, len - 1).mSum;
}
class wtevTree { //线段树
int lSum;// 以左区间为端点的最大子段和
int rSum;// 以右区间为端点的最大子段和
int iSum;// 区间所有数的和
int mSum;// 该区间的最大子段和
wtevTree(int l, int r, int i, int m) { // 构造函数
lSum = l;
rSum = r;
iSum = i;
mSum = m;
}
}
// 通过既有的属性,计算上一层的属性,一步步往上返回,获得线段树
wtevTree pushUp(wtevTree leftT, wtevTree rightT) {
// 新子段的lSum等于左区间的lSum或者左区间的 区间和 加上右区间的lSum
int l = Math.max(leftT.lSum, leftT.iSum + rightT.lSum);
// 新子段的rSum等于右区间的rSum或者右区间的 区间和 加上左区间的rSum
int r = Math.max(leftT.rSum + rightT.iSum, rightT.rSum);
// 新子段的区间和等于左右区间的区间和之和
int i = leftT.iSum + rightT.iSum;
// 新子段的最大子段和,其子段有可能穿过左右区间,或左区间,或右区间
int m = Math.max(leftT.rSum + rightT.lSum, Math.max(leftT.mSum, rightT.mSum));
return new wtevTree(l, r, i, m);
}
// 递归建立和获得输入区间所有子段的结构
wtevTree getInfo(int[] nums, int left, int right) {
if (left == right) // 若区间长度为1,其四个子段均为其值
return new wtevTree(nums[left], nums[left], nums[left], nums[left]);
int mid = (left + right) >> 1;// 获得中间点mid,右移一位相当于除以2,运算更快
wtevTree leftT = getInfo(nums, left, mid);
wtevTree rightT = getInfo(nums, mid + 1, right);//mid+1,左右区间没有交集。
return pushUp(leftT, rightT);//递归结束后,做最后一次合并
}
}
em...线段树其实我也没理解
动态规划的核心思想就是找到递推公式,在这个题中,随着遍历数组,一个变量随着遍历不断保存着当前最大和,一个变量MAX保存历史最大和,最后当前最大和和历史最大和进行比较即可。
入门数据结构第二天
两数之和(1,简单)
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
public class TwoSum {
public static int[] twoSum1(int[] nums, int target) {
int n = nums.length;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
//分层遍历,然后去寻找两数相加等于目标值的那两个数
if (nums[i] + nums[j] == target) {
System.out.println(""+nums[i]+"和"+nums[j]);
return new int[]{i,j};
}
}
}
return new int[0];
}
public static int[] twoSum2(int[] nums, int target) {
Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; ++i) {
if (hashtable.containsKey(target - nums[i])) {
//找到了目标值减去num[x]的值
System.out.println(""+nums[i]+"和"+nums[hashtable.get(target - nums[i])]);
return new int[]{hashtable.get(target - nums[i]), i};
}
hashtable.put(nums[i], i);
}
return new int[0];
}
public static void main(String[] args) {
int[] sum = {1,5,7,8,59,57,25,14,24,23};
twoSum1(sum,47);
twoSum2(sum,47);
}
}
合并两个有序数组(88.简单)
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
public class Merge {
public static void merge1(int[] nums1, int m, int[] nums2, int n) {
//暴力求值
for (int i = 0; i != n; ++i) {
nums1[m + i] = nums2[i];
}
Arrays.sort(nums1);
System.out.println(Arrays.toString(nums1));
}
public static void merge2(int[] nums1, int m, int[] nums2, int n) {
//利用了双向指针,把这两个数组看成跟队列一样,依次按序入队就行
int p1 = 0, p2 = 0;
int[] sorted = new int[m + n];
int cur;
while (p1 < m || p2 < n) {
if (p1 == m) {
cur = nums2[p2++];
} else if (p2 == n) {
cur = nums1[p1++];
} else if (nums1[p1] < nums2[p2]) {
cur = nums1[p1++];
} else {
cur = nums2[p2++];
}
sorted[p1 + p2 - 1] = cur;
}
for (int i = 0; i != m + n; ++i) {
nums1[i] = sorted[i];
}
System.out.println(Arrays.toString(nums1));
}
public static void main(String[] args) {
int[] sums3 = new int[10];sums3[0]=1;
sums3[1]=2;
sums3[2]=3;
sums3[3]=4;
sums3[4]=4;
int[] sums4 = {6,7,8,9,10};
merge1(sums3,5,sums4,5);
merge2(sums3,5,sums4,5);
}
}
入门数据结构第三天
(两个数组的交际II 350 简单)
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
我们可以不考虑输出结果的顺序。
进阶:如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
/给定两个数组,编写一个函数来计算它们的交集。
// 示例 1:
//
// 输入:nums1 = [1,2,2,1], nums2 = [2,2]
// 输出:[2,2]
// 示例 2:
//
// 输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
// 输出:[4,9]
public class Intersect {
public static int[] intersect(int[] nums1,int[] nums2) {
if (nums1.length > nums2.length) {
//先进行比较,确保传进来的第一个数组的长度小于第二个数组的长度,
// 为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集。
return intersect(nums2, nums1);
}
//声明一下Map集合
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int num : nums1) {
//采用了for循环,挨个纪录数组中每个元素以及他们出现的次数
int count = map.getOrDefault(num, 0) + 1;
//这个map的方法的意思是,如果map集合中含有num元素,返回他的value值,如果没有,返回defaultValue值也就是0
map.put(num, count);
//将更改后的数组元素放入map中,循环结束,map中key放的是nums1中的所有元素,value是这些元素在出现的次数
}
int[] intersection = new int[nums1.length];
//声明一个数组,这个数组的长度是nums1的长度
int index = 0;
for (int num : nums2) {
int count = map.getOrDefault(num, 0);
//先进行判断,找出来nums2中的元素有没有在nums1中出现过
if (count > 0) {
//如果出现过,将他放进我们的Intersection中,注意,index++是先执行语句,执行完在index+1
intersection[index++] = num;
count--;//这时候需要减一了,因为此时此刻map中的num元素被放到了intersection数组当中
if (count > 0) {
//这是count如果还大于0,那就将更新后的count放进map
map.put(num, count);
fun1(map);
} else {
//此时如果count小于等于0,就直接把它从map集合中移出
map.remove(num);
}
}
}
return Arrays.copyOfRange(intersection, 0, index);
}
//第二个方法
public static int[] intersect2(int[] nums1, int[] nums2){
//先对传进来的两个数组进行排序
//本题采用了双指针来解决这个问题
Arrays.sort(nums1);
Arrays.sort(nums2);
//将这两个数组的长度都分别发给length1 length2中
int length1 = nums1.length,length2 = nums2.length;
//声明一个数组intersection,将nums1,nums2中更小的那一个长度赋给它
int[] intersection = new int[Math.min(length1,length2)];
int index1 = 0,index2 = 0,index = 0;
while(index1 < length1 && index2 < length2){
//如果其中一个数组的指针指向的位置超过了长度那就退出循环
if(nums1[index1] < nums2[index2]){
//如果数组1的数小于数组2的那就数组1继续想右遍历
index1 ++;
}else if(nums1[index1] > nums2[index2]){
//如果数组2的值小于数组1的值,那就数组2继续向右遍历
index2++;
} else{
//如果最终相等的话,那就吧这个相等的数赋给intersection数组
intersection[index] = nums1[index1];
index1++;
index2++;
index++;
}
}
return Arrays.copyOfRange(intersection,0,index);
}
public static void main(String[] args) {
int[] sum1 = {1, 2, 3, 3, 4, 5};
int[] sum2 = {1, 2, 3, 3, 4, 5, 6, 7};
intersect(sum1, sum2);
}
//这个是用来打印map集合的方法
public static void fun1(Map<Integer,Integer> map){
//keySet() 返回此映射中所包含的键的 Set 视图。 获取key的set集合
Set<Integer> set= map.keySet();
Iterator iterator=set.iterator();
while (iterator.hasNext()){
Object key=iterator.next();
System.out.print("map的key是"+key+"\n");
System.out.print("map的value是"+map.get(key)+"\n");
System.out.println();
}
}
}
补充一下关于map的相关知识
Map map = new HashMap(); //创建map
1、添加
map.put("1","小说")
putAll(Map<? extends K,? extends V> m) //从指定映射中将所有映射关系复制到此映射中(可选操作)。
2、删除
map.remove("1") //删除指定key的键值对,返回被删除的key关联的value,不存在返回null
map.remove("1","小说") //删除指定的键值对,成功返回true
map.clear() //删除map中的所有键值对
3、计数
map.size() //返回map中键值对的个数
4、判断
map.isEmpty() //判断是否为空
map.containsKey("1") //判断Map中是否包含指定的Key
map.containsValue("小说"); //判断Map中是否包含指定的Value
5、取值
map.get("1") //返回指定Key对应的Value,不存在则返回null
Collection set = map.entrySet() //返回Map中键值对所组成的Set集合,集合元素是Map.Entry(Map的内部类)对象
Collection set = map.keySet() //返回Map中的key的Set集合
Collection set = mat.values() //返回的是Map里所有的value组成的Collection
第一种方式:使用keySet:
将Map转成Set集合(keySet()),通过Set的迭代器取出Set集合中的每一个元素(Iterator)就是Map集合中的所有的键,再通过get方法获取对应的值。
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "aaaa");
map.put(2, "bbbb");
map.put(3, "cccc");
System.out.println(map);
//
// 获取方法:
// 第一种方式: 使用keySet
// 需要分别获取key和value,没有面向对象的思想
// Set<K> keySet() 返回所有的key对象的Set集合
Set<Integer> ks = map.keySet();
Iterator<Integer> it = ks.iterator();
while (it.hasNext()) {
Integer key = it.next();
String value = map.get(key);
System.out.println("key=" + key + " value=" + value);
}第二种方式: 通过values 获取所有值,不能获取到key对象:
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "aaaa");
map.put(2, "bbbb");
map.put(3, "cccc");
第二种方式: 通过values 获取所有值,不能获取到key对象
Collection<V> values()
Collection<String> vs = map.values();
Iterator<String> it = vs.iterator();
while (it.hasNext()) {
String value = it.next();
System.out.println(" value=" + value);
}
第三种方式: Map.Entry
public static interface Map.Entry<K,V> 通过Map中的entrySet()方法获取存放Map.Entry<K,V>对象的Set集合。
Set<Map.Entry<K,V>> entrySet() 面向对象的思想将map集合中的键和值映射关系打包为一个对象,就是Map.Entry,将该对象存入Set集合,Map.Entry是一个对象,那么该对象具备的getKey,getValue获得键和值。Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "aaaa");
map.put(2, "bbbb");
map.put(3, "cccc");
// 第三种方式: Map.Entry对象 推荐使用 重点
// Set<Map.Entry<K,V>> entrySet()
// 返回的Map.Entry对象的Set集合 Map.Entry包含了key和value对象
Set<Map.Entry<Integer, String>> es = map.entrySet();
Iterator<Map.Entry<Integer, String>> it = es.iterator();
while (it.hasNext()) {
//返回的是封装了key和value对象的Map.Entry对象
Map.Entry<Integer, String> en = it.next();
// 获取Map.Entry对象中封装的key和value对象
Integer key = en.getKey();
String value = en.getValue();
System.out.println("key=" + key + " value=" + value);
}
买卖股票的最佳时间(121 简单)
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
public class MaxProfit {
public int maxProfit1(int prices[]){
int mp = 0;
//最大利润声明一下是mp
for(int i = 0; i < prices.length-1 ; i++){
//暴力求解,直接吧所有的可能都列出来,两两相比较,最后会超时!
for(int j = i - 1; j < prices.length; j++){
int profit = prices[j] -prices[i];
if(profit > mp){
mp = profit;
}
}
}
return mp;
}
public static int maxProfit2(int prices[]){
//这个题的核心思想就是,利用了股票最大利润就是最低买入最高卖出
//想要利润最高只能在最低点买入,最高点卖出
//第二种方法 我们只需要遍历一边就能找到,随着for循环的遍历更新最小价格以及最大利润,随着遍历结束最大利润求出来了
int miniprice = Integer.MAX_VALUE;
//定义一个最大值,确保第一天的价格能成为最小价格。
int maxprofit = 0;
for(int i = 0; i < prices.length; i ++){
//挨个遍历比较,找出来股票的最小价格
if(prices[i] < miniprice){
miniprice = prices[i];
} else if((prices[i] - miniprice) > maxprofit){
//用当日价格减去最小价格与最大利润进行比较,给最大利润赋值。
maxprofit = prices[i] - miniprice;
}
System.out.println(maxprofit+"最大利润");
System.out.println(miniprice+"最小价格");
}
return maxprofit;
}
public static void main(String[] args) {
int[] nums = {7,8,5,3,6,1,4,9,2};
maxProfit2(nums);
}
}
数据结构入门第四天
(566 重塑矩阵 简单)
在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。
给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 r 和 c ,分别表示想要的重构的矩阵的行数和列数。
重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。
如果具有给定参数的 reshape 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。
示例 1:
输入:mat = [[1,2],[3,4]], r = 1, c = 4
输出:[[1,2,3,4]]
示例 2:
输入:mat = [[1,2],[3,4]], r = 2, c = 4
输出:[[1,2],[3,4]]
public class matrixReshape {
public int[][] matrixRes(int[][] nums, int r,int c){
int m = nums.length;
//二维数组中,nums.length表示的含义是行的数量,nums[0].length表示的是列的数量
int n = nums[0].length;
if(m * n != r * c){
return nums;
}
int [][] ans = new int[r][c];
for(int x = 0; x < m * n; ++x){
ans[x/c][x%c] = nums[x/n][x%n];
//这里x/c算出来就是行的下标,x%c算出来对应的是列的坐标
}
return ans;
}
}
(118 杨辉三角 简单)
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
示例 2:
输入: numRows = 1
输出: [[1]]
public class generate {
//本题就使用动态规划解题
//首先确定边界值,随后确定我们的递推公式
// arr[0][0] == 1 arr[0][1] == 1 arr[1][0] == 1
// arr[i][j] == a[i-1][j] + a[i-1][j-1] 在col=0或者row == col 时 把值变成1
public static List<List<Integer>> Generate(int numRows){
int[][] arr = new int[numRows][numRows];
List<List<Integer>> ans = new ArrayList<>();
//声明一个大集合,把每个小集合都放进去,题目要求的。
for(int row = 0; row < numRows; row ++){
List<Integer> rowAns = new ArrayList<>();
//声明一个集合,将每个遍历出的结果arr[][]放进去
for(int col = 0; col <= row; col++){
if(col == 0 || row == col){
arr[row][col] = 1;
}
else {
arr[row][col] = arr[row - 1][col - 1] + arr[row-1][col];
}
rowAns.add(arr[row][col]);
}
ans.add(rowAns);
System.out.println(rowAns);
}
System.out.println(ans);
return ans;
}
public static void main(String[] args) {
Generate(5);
}
}