【算法】实验室2024第一次考核复盘
本篇博客将遵循从易到难的原则,依次解析这几道考核题目。
原题链接:
125. 验证回文串 - 力扣(LeetCode)
使用两个指针i
和j
分别从字符串的两端开始,i
从左往右,j
从右往左。通过两个while循环,i
和j
会跳过所有非字母和非数字的字符。当i
小于j
时,比较i
和j
指向的字符(转换为小写后),如果不相等,则直接返回false
,表示字符串不是回文。
如果相等,则i
和j
各自向中间移动一位,继续比较下一对字符。
如果所有对应的字符都相等,当i
大于等于j
时,循环结束,返回true
,表示字符串是回文。
class Solution {
public boolean isPalindrome(String s) {
int i = 0, j = s.length() - 1;
while(i < j){
while(i < j && !Character.isLetterOrDigit(s.charAt(i))) i++;
while(i < j && !Character.isLetterOrDigit(s.charAt(j))) j--;
if(Character.toLowerCase(s.charAt(i)) != Character.toLowerCase(s.charAt(j))) return false;
i++;
j--;
}
return true;
}
}
原题链接:
392. 判断子序列 - 力扣(LeetCode)
如果s
的长度为0,即s
为空字符串,那么它自然地是任何字符串的子序列,因此返回true
。
使用两个指针i
和j
分别遍历字符串s
和t
。i
从0开始,j
也从0开始。
当j
小于t
的长度时,循环继续。在每次循环中,比较s
中的当前字符(s.charAt(i)
)和t
中的当前字符(t.charAt(j)
)。
如果这两个字符相等,i
增加1(指向s
的下一个字符),并且检查是否已经遍历完s
的所有字符。如果是,则返回true
,因为s
是t
的子序列。
如果j
遍历完t
而i
没有遍历完s
,则s
不是t
的子序列,方法返回false
。
class Solution {
public boolean isSubsequence(String s, String t) {
if (s.length() == 0) return true;
for (int i = 0, j = 0; j < t.length(); j++) {
if (s.charAt(i) == t.charAt(j)) {
// 若已经遍历完 s ,则提前返回 true
if (++i == s.length())
return true;
}
}
return false;
}
}
原题链接:
35. 搜索插入位置 - 力扣(LeetCode)
使用两个指针left
和right
来表示搜索区间,初始时left
为0,right
为数组长度。
使用while
循环进行二分查找,当left
小于right
时循环继续。
在每次循环中,计算中间索引mid
,然后比较nums[mid]
与target
的值。
如果nums[mid]
等于target
,则找到了目标值,但代码中缺少了返回mid
的语句。
如果nums[mid]
小于target
,则将left
设置为mid + 1
,表示搜索区间移动到mid
的右侧。
如果nums[mid]
大于target
,则将right
设置为mid
,表示搜索区间移动到mid
的左侧。
当left
和right
相遇时,循环结束,此时left
即为target
应该插入的位置。
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length;
while(left < right) {
int mid = (left + right) / 2;
if(nums[mid] == target) {
} else if(nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return 0;
}
}
原题链接:
147. 对链表进行插入排序 - 力扣(LeetCode)
创建一个哑节点(dummy node)dummy
,其值设置为0,它的作用是简化边界情况的处理,因为哑节点不包含在链表的真实元素中,但是可以作为头节点的前一个节点。
使用pre
指针指向哑节点,cur
指针从head
开始遍历链表。
对于链表中的每个节点(cur
),在dummy
到pre
之间找到合适的插入位置。这是通过内部的while
循环实现的,循环条件是pre.next
不为空且pre.next.val
小于cur.val
。
找到插入位置后,将cur
节点插入到pre.next
之前。
更新pre
为哑节点,以便为下一个节点的插入做准备。
更新cur
为下一个节点,继续遍历链表。
最后,返回哑节点的下一个节点dummy.next
,即为排序后的链表的头节点。
class Solution {
public ListNode insertionSortList(ListNode head) {
ListNode dummy = new ListNode(0);
ListNode pre = dummy;
ListNode cur = head;
while (cur != null) {
ListNode tmp = cur.next;
while (pre.next != null && pre.next.val < cur.val) pre = pre.next;
cur.next = pre.next;
pre.next = cur;
pre = dummy;
cur = tmp;
}
return dummy.next;
}
}
原题链接:
125. 验证回文串 - 力扣(LeetCode)
遍历字符串中的每个字符,对于大写字母,将其转换为小写(通过ASCII值减去32实现),对于小写字母和数字,直接保留。
过滤掉非字母数字字符,并在过滤后的字符数组中更新字符位置,通过index
指针来记录有效字符的个数。
使用两个指针left
和right
分别从过滤后的字符数组的两端开始,向中间遍历。
如果两端的字符不相等,则返回false
,表示字符串不是回文。
如果所有对应的字符都相等,循环结束后返回true
,表示字符串是回文。
class Solution {
public boolean isPalindrome(String str) {
char[] s = str.toCharArray();
int n = s.length;
int index = 0;
for (int i = 0; i < n; i++) {
if (s[i] >= 'A' && s[i] <= 'Z') {
// 大写
s[index++] = (char) (s[i] + 32);
} else if ((s[i] >= 'a' && s[i] <= 'z') ||
(s[i] <= '9' && s[i] >= '0')) {
// 小写和数字
s[index++] = s[i];
}
}
// System.out.println(new String(s, 0, index));
int left = 0, right = index - 1;
while (left < right) {
if (s[left] != s[right]) return false;
left++;
right--;
}
return true;
}
}
原题链接:
61. 旋转链表 - 力扣(LeetCode)
找到插入点,分开两个链表,再首尾相接即可。
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(head == null|| k == 0){
return head;
}
ListNode fast = head;
ListNode slow = head;
int n = 0;
while(fast != null){
fast = fast.next;
n++;
}
if(k % n == 0){
return head;
}
fast = head;
for(int i = k % n;i >= 0;i--){
fast = fast.next;
}
while(fast != null){
fast = fast.next;
slow = slow.next;
}
//qiefen
ListNode newHead = slow.next;
slow.next = null;
ListNode pmov = newHead;
while(pmov != null && pmov.next != null){
pmov = pmov.next;
}
pmov.next = head;
return newHead;
}
}
原题链接:
232. 用栈实现队列 - 力扣(LeetCode)
在【算法】栈作队时队亦栈,进为出处出也进-CSDN博客
这篇博客中,我对本题作了比较详细的阐述,就不再赘述啦~
typedef struct {
int stackIn[200];
int stackOut[200];
int topIn;
int topOut;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* que = (MyQueue*)malloc(sizeof(MyQueue));
que->topIn = 0;
que->topOut = 0;
return que;
}
void myQueuePush(MyQueue* obj, int x) {
obj->stackIn[obj->topIn] = x;
obj->topIn = obj->topIn+1;
}
int myQueuePop(MyQueue* obj) {
//将in栈中元素压入out栈
int inTop = obj->topIn;
int outTop = obj->topOut;
if (outTop == 0) {
while (inTop > 0) {
obj->stackOut[outTop++] = obj->stackIn[--inTop];
}
}
//出栈(出队)
int top = obj->stackOut[--outTop];
//将out栈中元素压回in栈
while (outTop > 0) {
obj->stackIn[inTop++] = obj->stackOut[--outTop];
}
obj->topIn = inTop;
obj->topOut = outTop;
return top;
}
int myQueuePeek(MyQueue* obj) {
//队列首个元素即为in栈中最底下的元素
return obj->stackIn[0];
}
bool myQueueEmpty(MyQueue* obj) {
if(obj->topIn == 0){
return true;
}else{
return false;
}
}
void myQueueFree(MyQueue* obj) {
obj->topIn = 0;
obj->topOut = 0;
}
原题链接:
309. 买卖股票的最佳时机含冷冻期 - 力扣(LeetCode)
如果数组长度小于等于1,意味着没有交易发生,返回0作为利润。
创建一个二维整数数组dp,其中dp[i][j]表示在第i天时,采用第j种策略能够获得的最大利润。数组的三个状态分别代表:
dp[i][0]
:第i
天不持有股票的最大利润。dp[i][1]
:第i
天持有股票的最大利润。dp[i][2]
:第i
天已经卖出股票后的最大利润(即在第i
天或之前卖出)。
初始化dp[0]
数组,其中dp[0][0]
为0(不持有股票),dp[0][1]
为-prices[0]
(购买股票),dp[0][2]
为0(已经卖出股票)。
遍历price数组,从第1天到第n-1天,更新dp数组。对于每一天i:
dp[i][0]
更新为不持有股票的最大利润,即持有或不持有股票的最大利润(dp[i-1][0]
或dp[i-1][2]
)。dp[i][1]
更新为持有股票的最大利润,即前一天不持有股票的情况下减去当前天股票价格(dp[i-1][0] - prices[i]
)或前一天持有股票的最大利润(dp[i-1][1]
)。dp[i][2]
更新为卖出股票后的最大利润,即持有股票的情况下加上当前天股票价格(dp[i-1][1] + prices[i]
)。
最后,返回第n-1
天不持有股票或已经卖出股票的最大利润(dp[n-1][0]
或dp[n-1][2]
)。
class Solution {
public int maxProfit(int[] prices) {
int n=prices.length;
if(n<=1) return 0;
int [][] dp=new int[n][3];
dp[0][0]=0;
dp[0][1]=-1*prices[0];
dp[0][2]=0;
for(int i=1;i<n;i++){//从[1]...[n-1]
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][2]);
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
dp[i][2]=dp[i-1][1]+prices[i];
}
return Math.max(dp[n-1][0],dp[n-1][2]);
}
}
原题链接:
238. 除自身以外数组的乘积 - 力扣(LeetCode)
如果数组长度为0,则直接返回空数组,因为没有任何元素可以计算乘积。
创建一个与nums
同样长度的数组ans
,用于存储每个元素的乘积结果。数组的第一个元素初始化为1,因为任何数与1相乘都等于其本身。
使用一个临时变量tmp
来存储从数组末尾开始的累积乘积。
首先,通过一个循环计算出ans
中每个元素除了其自身以外左侧所有元素的乘积。
然后,通过另一个循环从数组末尾开始,更新ans
中每个元素的乘积,使其包含右侧所有元素的乘积。
class Solution {
public int[] productExceptSelf(int[] nums) {
int len = nums.length;
if (len == 0) return new int[0];
int[] ans = new int[len];
ans[0] = 1;
int tmp = 1;
for (int i = 1; i < len; i++) {
ans[i] = ans[i - 1] * nums[i - 1];
}
for (int i = len - 2; i >= 0; i--) {
tmp *= nums[i + 1];
ans[i] *= tmp;
}
return ans;
}
}
原题链接:
73. 矩阵置零 - 力扣(LeetCode)
获取矩阵的行数m
和列数n
。
创建两个辅助数组hashm
和hashn
,分别用于记录哪些行和列需要被置为0。
首先,遍历整个矩阵,如果发现某个元素为0,则在hashm
和hashn
中标记对应的行和列。
然后,遍历hashm
,如果某个索引的值为1,则将矩阵中对应行的所有元素置为0。
最后,遍历hashn
,如果某个索引的值为1,则将矩阵中对应列的所有元素置为0。
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
int[] hashm = new int[m];
int[] hashn = new int[n];
for(int i = 0;i < m;++i){
for(int j = 0;j < n;++j){
if(matrix[i][j] == 0){
hashm[i] = 1;
hashn[j] = 1;
}
}
}
for(int i = 0;i < m;++i){
if(hashm[i] == 1){
System.out.println(i);
for(int z = 0;z < n;++z){
matrix[i][z] = 0;
}
}
}
for(int j = 0;j < n;++j){
if(hashn[j] == 1){
System.out.println(j);
for(int z = 0;z < m;++z){
matrix[z][j] = 0;
}
}
}
}
}
原题链接:
394. 字符串解码 - 力扣(LeetCode)
使用一个栈st
来存储解码过程中的字符和数字。
使用变量cnt
来记录当前处理到字符串s
的哪个位置。
使用变量ans
来存储最终解码后的字符串。
使用循环遍历字符串s,根据当前字符tmp的类型进行不同的处理:
- 如果
tmp
是字母或数字,将其压入栈st
。 - 如果
tmp
是"["
,将其压入栈,并继续读取下一个字符。 - 如果tmp是"]",开始解码操作:
- 弹出栈中直到遇到
"["
的所有字符,并反转这个字符串,得到需要重复的字符串strtmp
。 - 继续弹出栈中直到所有数字被弹出,反转这个字符串得到
numstr
。 - 将
numstr
转换为整数num
,表示需要重复的次数。 - 将
strtmp
按照num
次重复,并将重复后的字符压回栈中。
- 弹出栈中直到遇到
- 如果
tmp
不在上述情况中,循环继续。
class Solution {
public String decodeString(String s) {
Stack<Character> st = new Stack<>();
String ans = "";
int cnt = 0;
while(cnt != s.length()){
char tmp = s.charAt(cnt);
//如果是数字
if(tmp <= '9' || tmp >= 'a'){
st.push(tmp);
cnt++;
}else if(tmp == '['){
st.push(tmp);
cnt++;
}else if(tmp == ']'){
cnt++;
String strtmp = "";
while(st.peek() != '['){
strtmp += st.pop();
}
st.pop();
String numstr = "";
while(!st.isEmpty() && st.peek() <= '9'){
numstr += st.pop();
}
String numstrtrue = "";
int n = numstr.length()-1;
while(n >= 0){
numstrtrue += numstr.charAt(n);
n--;
}
int num = Integer.parseInt(numstrtrue);
for(int i = 0;i < num;++i){
for(int j = strtmp.length()-1;j >= 0;--j){
st.push(strtmp.charAt(j));
}
}
}else{
continue;
}
}
for(char ch : st){
ans += ch;
}
return ans;
}
}
原题链接:
23. 合并 K 个升序链表 - 力扣(LeetCode)
合并两个有序链表升级版~
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length == 0) return null;
if(lists.length == 1) return lists[0];
for(int i = 1;i < lists.length;++i){
lists[0] = MergeLists(lists[0],lists[i]);
}
return lists[0];
}
public ListNode MergeLists(ListNode l1,ListNode l2){
ListNode dummy = new ListNode(0);
ListNode pmov = dummy;
while(l1 != null && l2 != null){
if(l1.val < l2.val){
pmov.next = l1;
l1 = l1.next;
}else{
pmov.next = l2;
l2 = l2.next;
}
pmov = pmov.next;
}
if(l1 == null){
pmov.next = l2;
}else{
pmov.next = l1;
}
return dummy.next;
}
}