Leetcode 数组部分笔记
1. 合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
解题思路: 第一种方法 直接用归并。
第二种方法:设置三个指针,分别指向num1的最后,num1最后一个有效数据,num2最后一个有效数据。只要num1和num2不为空则进行遍历。如果num1和num2都不为空,则进行比较判断。如果num2为空,则把num1剩下的放进原数组。如果num2为空,则把num2剩下的放进原数组。
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int tail = m+n-1;
int t1 = m-1;
int t2 = n-1;
while(t1>=0||t2>=0){
if(t1>=0&&t2>=0){
if(nums1[t1]>=nums2[t2]){
nums1[tail] = nums1[t1];
tail--;
t1--;
}else{
nums1[tail] = nums2[t2];
tail--;
t2--;
}
}else if(t1<0){
nums1[tail] = nums2[t2];
tail--;
t2--;
}else if(t2<0){
nums1[tail] = nums1[t1];
tail--;
t1--;
}
}
}
}
2. 移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
返回 k。
解题思路: 设置两个指针,一个在数组头,一个在数组尾,如果数组头元素等于target,则替换到后面,数组头指针不变,继续判断,数组尾指针向前移动。如果不等于,则数组头指针向后移动,数组尾指针不动。
class Solution {
public int removeElement(int[] nums, int val) {
int n = nums.length-1;
int i = 0;
while(i <= n){
if(nums[i] == val){
int tmp = nums[i];
nums[i] = nums[n];
nums[n] = tmp;
n--;
}else{
i++;
}
}
return n+1;
}
}
3. 删除有序数组中的重复项
给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:
更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
解题思路: 设置两个指针,一个慢指针用于存储,一个快指针用于检查,当快指针与快指针前一个元素相等时,则直接让快指针向后移动。如果不相等,快指针元素值赋给慢指针+1的位置,慢指针和快指针继续移动。
class Solution {
public int removeDuplicates(int[] nums) {
int slow = 0;
int fast = 1;
int tail = nums.length;
while(fast<nums.length){
if(nums[fast]!=nums[fast-1]){
nums[slow+1] = nums[fast];
slow++;
fast++;
}else{
fast++;
}
}
return slow+1;
}
}
4. 删除有序数组中的重复项 II
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
解题思路: 同理设置一个快指针一个慢指针,这次慢指针和快指针共同判断是否重复,还是用慢指针存储元素,快指针继续向后移动。初始从2开始,因为最多可重复两次。如果慢指针-2 等于了 快指针的值,则快指针向后移动。否则,慢指针的值等于快指针的值,快指针和慢指针都向后移动。
class Solution {
public int removeDuplicates(int[] nums) {
int slow = 2;
int fast = 2;
while(fast < nums.length){
if(nums[fast] != nums[slow-2]){
nums[slow] = nums[fast];
fast++;
slow ++;
}else{
fast++;
}
}
return slow;
}
}
5. 多数元素(求众数)
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length/2];
}
}
6. 轮转数组
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
解题思路: 建立一个新数组用于存储原本的数组,因为轮转的本质就是(i + k) % len,所以让nums[(i+k)%(nums.length)] = nums1[i]即可。
class Solution {
public void rotate(int[] nums, int k) {
int[] nums1 = Arrays.copyOf(nums,nums.length);
for(int i = 0; i < nums.length; i++){
nums[(i+k)%(nums.length)] = nums1[i];
}
}
}
7. 买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
解题思路: 遍历数组的同时,记录遍历过程中最小的数,让遍历中的数和最小的数求差,并求最大值。
class Solution {
public int maxProfit(int[] prices) {
// 用于存储最大差值返回
int maxprice = 0;
// 用于存储当前最小钱数
int current = prices[0];
for(int i = 0; i < prices.length; i++){
current = Math.min(current,prices[i]);
maxprice = Math. max(maxprice,prices[i] - current);
}
return maxprice;
}
}
8. 买卖股票的最佳时机 II
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
解题思路:也就是求整个数组后面一个数比前面一个数大的情况下的差值之和。
也就是遍历数组,求当前遍历到的数和前一个数字的差值和0取最大值,在加到res中。
class Solution {
public int maxProfit(int[] prices) {
int ans = 0;
int rest = 0;
for(int i = 1;i < prices.length; i++){
rest = Math.max(prices[i] - prices[i-1], 0);
ans = ans + rest;
}
return ans;
}
}
9. 跳跃游戏
给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。
解题思路: 遍历数组,判断当前遍历到的位置和到目前为止最大能到达的位置,如果当前位置大于最大能到的位置,则必然无法到达。否则能到达。
class Solution {
public boolean canJump(int[] nums) {
int total = 0;
int n = nums.length;
for(int i = 0; i<n;i++){
if(i > total){
return false;
}else{
total = Math.max(total,nums[i]+i);
}
}
return true;
}
}
10. 跳跃游戏 II
给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。
每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:
解题思路: 遍历数组,判断当前位置最多一下能到哪个位置,遍历过程中持续记录最多能到哪个位置,如果遍历到了最多能到达的位置,则让走的步数+1,让end = 最多能走到的步数。遍历结束返回走多少步。
class Solution {
public int jump(int[] nums) {
int k = 0;
int step = 0;
int end = 0;
for(int i = 0; i< nums.length-1; i++){
k = Math.max(k,i+nums[i]);
if(i==end){
step++;
end = k;
}
}
return step;
}
}
11. H指数
给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。
根据维基百科上 h 指数的定义:h 代表“高引用次数” ,一名科研人员的 h 指数 是指他(她)至少发表了 h 篇论文,并且 至少 有 h 篇论文被引用次数大于等于 h 。如果 h 有多种可能的值,h 指数 是其中最大的那个。
解题思路: 给你一个数组,求一个最大的 h,使得数组中有至少 h 个数都大于等于 h。设置一个h,一个i,i用来从大到小遍历数组,h用来计数,当h的值>=当前遍历的值,则暂停,返回当前的h。
class Solution {
public int hIndex(int[] citations) {
// 先排个序 从小到大排序
// 主要思想
// h主要用来记录有几个比当前i对应的数大的
// i主要是从大到小去访问最大的可能
Arrays.sort(citations);
int h = 0;
int i = citations.length-1;
while (i>=0&&h<citations[i]){
i--;
h++;
}
return h;
}
}
12. O(1) 时间插入、删除和获取随机元素
实现RandomizedSet 类:
RandomizedSet() 初始化 RandomizedSet 对象
bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。
bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false 。
int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。
解题思路: 采用列表 + 哈希表的形式,实现O(1),用哈希表存储(Val,index),用集合保存最终结果。
1.初始化:new 三个对象。
2. 插入操作:先判断是不是存在,使用map.containsKey(val),如果不存在则执行插入操作,先获取index,让list 插入val,让Map记录 val和index,返回。
3. 删除操作:先判断是否存在,如果存在则得到该值索引、列表中最后一个值。让已经删除的索引位置存储列表中最后一个值,清除列表中最后一个值、清除map中已经删除的val。
4. 随机访问:随机获取一个0 —— nums.size()之间的一个值,再用列表进行获取。
class RandomizedSet {
List<Integer> nums;
Map<Integer, Integer> indices;
Random random;
public RandomizedSet() {
nums = new ArrayList<Integer>();
indices = new HashMap<Integer, Integer>();
random = new Random();
}
public boolean insert(int val) {
if (indices.containsKey(val)) {
return false;
}
int index = nums.size();
nums.add(val);
indices.put(val, index);
return true;
}
public boolean remove(int val) {
if (!indices.containsKey(val)) {
return false;
}
int index = indices.get(val);
int last = nums.get(nums.size() - 1);
nums.set(index, last);
indices.put(last, index);
nums.remove(nums.size() - 1);
indices.remove(val);
return true;
}
public int getRandom() {
int randomIndex = random.nextInt(nums.size());
return nums.get(randomIndex);
}
}
13. 除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
**解题思路:**使用前缀后缀,最终乘积的方法。以数组[1,2,3,4]为例。
前缀:初始化一个L数组,设置L[0] = 1,遍历数组,让L[i] = num[i-1] * L[i-1]。
L[0] = 1;
L[1] = 1;
L[2] = 2;
L[3] = 6;
后缀,初始化一个R数组,设置R[len - 1] = 1,并从len-2开始遍历。
R[3] = 1;
R[2] = 4;
R[1] = 12;
R[0] = 24;
这样再遍历一遍,对应索引相乘得到最终结果。
class Solution {
public int[] productExceptSelf(int[] nums) {
int length = nums.length;
// L 和 R 分别表示左右两侧的乘积列表
int[] L = new int[length];
int[] R = new int[length];
int[] answer = new int[length];
// L[i] 为索引 i 左侧所有元素的乘积
// 对于索引为 '0' 的元素,因为左侧没有元素,所以 L[0] = 1
L[0] = 1;
for (int i = 1; i < length; i++) {
L[i] = nums[i - 1] * L[i - 1];
}
// R[i] 为索引 i 右侧所有元素的乘积
// 对于索引为 'length-1' 的元素,因为右侧没有元素,所以 R[length-1] = 1
R[length - 1] = 1;
for (int i = length - 2; i >= 0; i--) {
R[i] = nums[i + 1] * R[i + 1];
}
// 对于索引 i,除 nums[i] 之外其余各元素的乘积就是左侧所有元素的乘积乘以右侧所有元素的乘积
for (int i = 0; i < length; i++) {
answer[i] = L[i] * R[i];
}
return answer;
}
}
14. 罗马数字转整数
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
解题思路: 设置一个hashmap,key为字符,value为数字,把罗马数字和数学数字的对应存储进去,然后遍历字符串,首先判断当前字符代表数字是否大于后一个数字,如果大于则加,如果小于则减。
class Solution {
public int romanToInt(String s) {
Map<Character,Integer> map = new HashMap<Character,Integer>();
map.put('I',1);
map.put('V',5);
map.put('X',10);
map.put('L',50);
map.put('C',100);
map.put('D',500);
map.put('M',1000);
int res = 0;
int i = 0;
for(i = 0; i< s.length()-1;i++){
if(map.get(s.charAt(i))<map.get(s.charAt(i+1))){
res = res - map.get(s.charAt(i));
}else{
res = res + map.get(s.charAt(i));
}
}
res = res + map.get(s.charAt(i));
return res;
}
}
15. 整数转罗马数字
解题思路: 使用StringBuffer存储罗马数字,append代表添加。使用数组存储个十百千的所有情况。让原本数字/1000得到千位数字,(原本数字%1000)/100得到百位数字。都放入Stringbuffer中。
class Solution {
public String intToRoman(int num) {
String[] thousands = {"", "M", "MM", "MMM"};
String[] hundreds = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
String[] tens = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
String[] ones = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};
StringBuffer s = new StringBuffer();
s.append(thousands[num/1000]);
s.append(hundreds[(num%1000)/100]);
s.append(tens[(num%100)/10]);
s.append(ones[(num%10)]);
return s.toString();
}
}
16. 最后一个单词的长度
给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。
单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。
解题思路: 用split分隔,再取最后一个的长度。
class Solution {
public int lengthOfLastWord(String s) {
String[] v = s.split(" ");
return v[v.length - 1].length();
}
}
17. 最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
解题思路: 建立辅助方法,寻找两个字符串的公共前缀,不遍历到头,并且两个索引的对应位置字符相同的情况下,让index++,记录最终的一个index,用substring返回。
公共前缀初始值设置为String[0],从1开始遍历字符串数组,公共前缀 = 求公共前缀函数(公共前缀,String[i]),如果公共前缀空了,直接返回“”,如果没空则继续执行,直到得到最终公共前缀。
class Solution {
public String longestCommonPrefix(String[] strs) {
// 如果字符串数组为空,直接返回空字符串
if (strs == null || strs.length == 0) {
return "";
}
// 初始公共前缀为第一个字符串
String commonPrefix = strs[0];
// 遍历剩余的字符串
for (int i = 1; i < strs.length; i++) {
// 比较当前字符串与当前最长公共前缀,更新最长公共前缀
commonPrefix = getCommonPrefix(commonPrefix, strs[i]);
// 如果最长公共前缀已经为空,直接返回
if (commonPrefix.equals("")) {
return "";
}
}
// 返回最长公共前缀
return commonPrefix;
}
// 辅助函数,用于获取两个字符串的公共前缀
private String getCommonPrefix(String str1, String str2) {
int index = 0;
// 找到两个字符串的公共前缀的长度
while (index < str1.length() && index < str2.length() && str1.charAt(index) == str2.charAt(index)) {
index++;
}
// 返回公共前缀
return str1.substring(0, index);
}
}
18. 反转字符串中的单词
给你一个字符串 s ,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
解题思路: 首先让字符串按空格切分为字符串数组,从尾开始遍历字符串数组,使用Stringbuilder保存结果,遍历过程中,先判断是否为单词(!=“” && len > 0) ,不是则continue,是则trim()清除空格,再append到builder中,再加一个空格。直到最后一个单词放入;。
最终返回builder.toString();因为最后多加个空格,用trim清除即可。
class Solution {
public String reverseWords(String s) {
String[] v = s.split(" ");
StringBuilder ans = new StringBuilder();
for(int i = v.length-1 ;i >= 0; i--){
v[i] = v[i].trim();
if(v[i] != " "&&v[i].length() > 0){
ans.append(v[i]);
ans.append(" ");
}else{
continue;
}
}
String res = ans.toString();
return res.trim();
}
}
19. Z字变换
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:
**解题思路:**创建一个ArrayList,里面存储StringBuilder类型,首先初始化列表每一行,一共numRow行,然后开始遍历字符串,先把遍历到的字符放入List[j] 的StringBuilder中,让flag = -1,判断索引j是否为0或者为numRow-1,如果是则flag = - flag,然后j = j + flag。当j到达numRow - 1时,flag = - flag,j = j + flag 就变成一个一个减了,最终得到j行List,然后遍历这个list,把每一行都放入一个新的StringBuilder中,返回结果。
class Solution {
public String convert(String s, int numRows) {
if(numRows < 2){
return s;
}
int j = 0;
StringBuilder res = new StringBuilder();
List<StringBuilder> a = new ArrayList<StringBuilder>();
// 初始化
for(int i = 0;i < numRows;i++) a.add(new StringBuilder());
int flag = -1;
for(char c : s.toCharArray()){
a.get(j).append(c);
if(j==0||j==numRows-1){
flag = -flag;
}
j = j + flag;
}
for(StringBuilder row : a) res.append(row);
return res.toString();
}
}
20. 找出字符串中第一个匹配项的下标
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
解题思路: 遍历长字符串,设置i为长字符串遍历指针,j为短字符串遍历指针。
如果长字符串[i] == 短字符串[j] 则判断j是否到达末尾,如果到达末尾,则返回i - j,也就是匹配的起始坐标。如果没到达末尾则判断j是否为0,如果为0,则说明是从当前的i开始匹配的,记录当前i的值。
如果长字符串 != 短字符串,则让j归为0,让i变为原本匹配的下一个,也就是Index ++。再次比对遍历。
class Solution {
public int strStr(String haystack, String needle) {
int l1 = haystack.length();
int l2 = needle.length();
int Index = 0;
int j = 0;
for(int i = 0; i<l1;i++){
if(haystack.charAt(i) != needle.charAt(j)){
j = 0;
i = Index++;
}else{
if(j == l2-1){
return i - j;
}else{
if(j == 0){
Index = i;
}
}
j++;
}
}
return -1;
}
}