面试笔试算法 - leetcode

简单题

快速求解区间和,使用前缀和

// 1 3 2 7 9 4
// 1 4 6 13 22 26
// 通过数组保存前缀和,可通过前缀和相减得出区间和

13 罗马数字,模拟

// switch 特判,对于 IV 类型位 + 1 额外判断下

14 最长公共前缀

string ans = strs[0];
for (int i = 1; i < strs.size(); ++i) {
	// 每一轮迭代继承上一轮的公共最长子序列
	string t = ans;
	ans = "";
	for (int j = 0; j < strs[i].size() && j < t.size(); ++j)
		if (str[i][j] == t[j]) ans += t[j];
	if (ans == "") break; // 不可能的情况直接退出
}

26 删除数组中重复项

int p = 1;
for (int i = 1; i < nums.size(); ++i) {
    if (nums[i] != nums[i - 1]) {
        nums[p] = nums[i];
        ++p;
    }
}
return p;

38 外观数列,模拟

// 每次输出都是继承上一次输出的情况
string ans = {"", "1"};
// s1 前一个字符串, s2 后一个字符串
void func(int s1, int s2) {
	int cnt = 0;
	for (int i = 0; i < ans[s1].size(); ++i) {
		if (cnt == 0 || ans[s1][i - 1] != ans[s1][i]) {
			cnt++;
		} else {
			// 按规则在其后输出
			ans[s2] += cnt + '0';
			ans[s2] += ans[s1][i - 1];
			cnt = 1;
		}
	}
	ans[s2] += cnt + '0';
	ans[s2] += ans[s1][ans[s1].size() - 1];
}

66 加一,进位

if (digits[i] == 10) {
	if (i != 0) {
	   digits[i - 1]++;
	} else {
	    digits.insert(digits.begin(), 1);
	}
}

70 爬楼梯,递归保存

// 保存效率约等于递推
int ans[50] = { 0, 1, 2 };
int climb(int n) {
	if (ans[n]) return ans[n];
	return ans[n] = climb(n - 1) + climb(n - 2);
}

88 合并有序数组

for (int i = m + n - 1; i > 0; --i) {
	// 考虑该判断式,一边结束后另一边数组继续
	if (m == 0 || n != 0 && num2[n - 1] > num1[m - 1]) {
		num1[i] = num2[n - 1]; n--;
	} else {
		num1[i] = num1[m - 1]; m--;
	}
}

118 杨辉三角

// 辅助数组保存位置记录

136 只出现一次数字,位运算抵消

for (int i = 0; i < nums.size(); ++i) {
  	ans ^= nums[i];
}

贪心

53 最大子序列和

int ans = INT_MIN, now = 0;
for (int i = 0; i < nums.size(); ++i) {
	// now 保存之前连续子序列加和,若正可再加,负的话重新开始
	now = min(nums[i], nums[i] + now);
	ans = max(ans, now);
}

121 买卖股票1

int ans = 0, mmin = prices[i];
for (int i = 1; i < prices.size(); ++i) {
	// 每次更新扣除之前最小值可能获得的最大理论
	ans = max(ans, prices[i] - mmin);
	mmin = min(mmin, prices[i]);
}

122 买卖股票2

// 只要后天股票价格比前天高就加
for (int i = 1; i < prices.size(); ++i) {
    if (prices[i] > prices[i-1]) {
        ans += prices[i] - prices[i-1];
    }
}

二分

278 0011情况

while (l != r) {
	int mid = (l + r) / 2;
	if (isBadVersion(mid)) {
		r = mid; // 右边可能
	} else {
		l = mid + 1; // 左边肯定不是
	}
}
// 输出 l r 位置均可

35 搜索插入位置 0011问题

while (l != r) {
	int mid = (l + r) / 2;
	if (nums[mid] >= target) {
		r = mid;
	} else {
		l = mid + 1;
	}
}

69 平方根,保留整数,1100问题

long long l = 0, r = x;
while (l != r) {
	int mid = (l + r + 1) / 2;
	if (mid * mid <= x) {
		l = mid;
	} else {
		r = mid - 1;
	}
}

递归,深度搜索

130 围绕区域改正,先深搜外围标记

// 需要边缘判断
void func(vector<vector<char>>& board, int x, int y) {
    board[x][y] = 'E';
    for (int i = 0; i < 4; ++i) {
        int xx = x + dir[i][0];
        int yy = y + dir[i][1];
        if (xx < 0 || yy < 0 || xx == n || yy == m || board[xx][yy] != 'O') continue;
        func(board, xx, yy);
   }
}

// 先在边缘入口转化外围字符为 E 最后修改 O 为 X,E 为 O

200 岛屿连接数量,每个点深搜加标记

// 标记是否去过
void func(vector<vector<char>>& grid, int x, int y) {
    for (int i = 0; i < 4; ++i) {
        int xx = x + dir[i][0];
        int yy = y + dir[i][1];
        if (xx < 0 || yy < 0 || xx == n || yy == m ) {
            continue;
        }
        if (grid[xx][yy] == '1' && !check[xx][yy]) {
            check[xx][yy] = 1;
            func(grid, xx, yy);
        }
    }
}
// 遍历进入的时候起点 ans++

967 连续差相等的数字

// 推入现在数字为多少 ... k 也可以写在外面作为全局变量
void func(int now, int left, int k) {
    // 剩余数字为0结束
    if (left == 0) {
        ans.push_back(now);
        return ;
    }
    // 往上加数字 k
    if (now % 10 + k < 10) {
        func(now * 10 + now % 10 + k, left - 1, k);
    }
    // 往下 - 数字 k
    if (k != 0 && now % 10 - k >= 0) {
        func(now * 10 + now % 10 - k, left - 1, k);
    }
}
// 每个起点递归
for (int i = 1; i < 10; ++i) {
    func(i, n - 1, k);
}

广度搜索

1091 记录到达目的最短步数

que.push((node){0,0,0});
while (!que.empty()) {
    node temp = que.front();
    que.pop();
    if (temp.x == n - 1 && temp.y == n - 1) {
        return temp.step + 1;
    }
    for (int i = 0; i < 8; ++i) {
        int x = temp.x + dir[i][0];
        int y = temp.y + dir[i][1];
        if (x < 0 || y < 0 || x == n || y == n) continue;
        if (grid[x][y] == 0 && !check[x][y]) {
            check[x][y] = 1;
            que.push((node){x, y, temp.step + 1});
        }
    }
}

994 向外感染

// 可以不标记直接修改原数组,注意特判新鲜橘子数量
int ans = 0;
while (!que.empty()) {
    node temp = que.front();
    que.pop();
    for (int i = 0; i < 4; ++i) {
        int x = temp.x + dir[i][0];
        int y = temp.y + dir[i][1];
        if (x < 0 || y < 0 || x == n || y == m) continue;
        if (grid[x][y] == 1) {
            grid[x][y] = 2;
            ans = max(ans, temp.step + 1);
            fresh--;
            que.push((node){x, y, temp.step + 1});
        }
    }
}

1662 对所有目标点距离最大


n = grid.size(), m = grid[0].size();
for (int i = 0; i < n; ++i) {
    for (int j = 0; j < m; ++j) {
        if (grid[i][j] == 1) {
            que.push((node) {i, j});
        }
    }
}
if (que.size() == n * m || que.size() == 0) return -1;
node temp;
// 通过队列所有 1 出来访问肯定是一层层靠近最终目标
while (!que.empty()) {
    temp = que.front();
    que.pop();
    for (int i = 0; i < 4; ++i) {
        int x = temp.x + dir[i][0];
        int y = temp.y + dir[i][1];
        if (x < 0 || y < 0 || x == n || y == m || grid[x][y] != 0) continue;
		// 通过修改 grid 控制访问过        
        grid[x][y] = grid[temp.x][temp.y] + 1; // 结构体无需添加 step
        que.push((node){x, y});
    }
}
return grid[temp.x][temp.y] - 1;

417 给定条件到边界

// 逆向思考,看从边界出发,有哪些点可以到达边界
// 先讲上左两侧陆地推入队列
// check 标记访问情况
for (int i = 0; i < m; i++) {
    que.push((node) {0, i});
    check[0][i] = 1;
}
for (int i = 1; i < n; ++i) {
    que.push((node) {i, 0});
    check[i][0] = 1;
}
// 第一次队列标记 check 改为 1
...
for (int i = 0; i < n; ++i) {
    que.push((node) {i, m - 1});
    check[i][m - 1] += 2;
}
for (int i = 0; i < m - 1; ++i) {
    que.push((node) {n - 1, i});
    check[n - 1][i] += 2;
}
// 第二次队列标记 check += 2 check == 3 说明都可以到达,退出

529 扫雷

// 返回周围雷数
int func(int x, int y, vector<vector<char>>& mmap) {
    int t = 0;
    for (int i = 0; i < 8; ++i) {
        int xx = x + dir[i][0];
        int yy = y + dir[i][1];
        if (xx < 0 || yy < 0 || xx == n || yy == m) {
            continue;
        }
        t += (mmap[xx][yy] == 'M');
    }
    return t;
}

// 只有空白会出发批量点开
if (func(click[0], click[1], board) == 0) {
    board[click[0]][click[1]] = 'B';
    que.push((node) {click[0], click[1]});
} else {
    board[click[0]][click[1]] = func(click[0], click[1], board) + '0';
    return board;
}
...
// 在 while 队列查找循环中同样是只点击空格的时候才继续推开
int t = func(x, y, board);
if (t != 0) {
    board[x][y] = t + '0';
} else {
    board[x][y] = 'B';
    que.push((node){x, y});
}

934 最短的桥,求最短距离

// 两个 for 先找出起点推入队列,然后标记(本体可改原地图来达到标记目的)
if (A[i][j] == 1) {
    A[i][j] = 2;
    que.push((node){i, j, 0});
    func(i, j, A);
    break;
}
...
// while 队列循环 再标记,若出现 A[x][y] == 1 到达目的返回

752 打开转盘锁所需最少步数

// 记录转盘状态
struct node {
    string status;
    int step;
};
...
// 对于转盘状态,广搜
for (int i = 0; i < 4; ++i) {
    string t = temp.status;
    t[i]++;
    if (t[i] > '9') {
        t[i] = '0';
    }
    if (t == target) {
        return temp.step + 1;
    }
    if (m[t] == 0) {
        m[t] = 1;
        que.push((node){t, temp.step + 1});
    }
    t = temp.status;
    t[i]--;
    if (t[i] < '0') {
        t[i] = '9';
    }
    if (t == target) {
        return temp.step + 1;
    }
    if (m[t] == 0) {
        m[t] = 1;
        que.push((node){t, temp.step + 1});
    }
}

864 获取钥匙的最短路径

struct node {
    int x, y, status, step;
};
// 关键 check 记录状态还要状态点的拿钥匙的状态记录
int n, m, check[35][35][200], key_cnt = 0, end_status;
// a-f 钥匙用位标记判开 end_status 根据 key_cnt 钥匙数量 为其中一位数
int bit2[10] = {1,2,4,8,16,32,64,128};

// ... 注意起点可走
if (mmap[i][j] == '@') {
    que.push((node) {i, j, 0, 0});
    check[i][j][0] = 1;
    mmap[i][j] = '.';
}
if (mmap[i][j] >= 'a' && mmap[i][j] <= 'f') {
    key_cnt++;
}
end_stats = bit2[key_cnt] - 1;
...
if (temp.status == end_status) {
    return temp.step;
}
...
// 循环内根据状态推入
if (x < 0 || y < 0 || x == n || y == m || check[x][y][temp.status] == 1) {
    continue;
}
check[x][y][temp.status] = 1;
if (mmap[x][y] == '.') {
    que.push((node) {x, y, temp.status, temp.step + 1});
} else if (mmap[x][y] >= 'a' && mmap[x][y] <= 'f') {
	// 该点位钥匙开启
    check[x][y][temp.status | bit2[mmap[x][y] - 'a']] = 1;
    que.push((node) {x, y, temp.status | bit2[mmap[x][y] - 'a'], temp.step + 1});
} else if (mmap[x][y] >= 'A' && mmap[x][y] <= 'F' && (temp.status & bit2[mmap[x][y] - 'A'])) {
    que.push((node) {x, y, temp.status, temp.step + 1});
}

顺序表和链表

19 删除倒数第 n 个节点

// 用两个标记,第一个标记先跑到第 n 个节点
// 然后两个标记同时往后,直到第一个标记到达末尾
// 删除第二个标记

24 两两交换链表

// 使用辅助头节点帮助定位关系
ListNode *first = new ListNode();
first->next = head;
ListNode *p1 = first;
ListNode *p2;
while (p1->next && p1->next->next) {
    p2 = p1->next;
    p1->next = p2->next;
    p2->next = p2->next->next;
    p1->next->next = p2;
    p1 = p2;
}
return first->next;

83 删除排序链表中重复元素

// 使用指针向后遍历
ListNode *p1 = head;
while (p1 && p1->next) {
    if (p1->val == p1->next->val) {
        ListNode *del = p1->next;
        p1->next = del->next;
        delete del;
    } else {
        p1 = p1->next;
    }
}
return head;

141 判断链表中是否存在环,快慢指针

ListNode *p = head;
ListNode *q = head;
while (p && p->next) {
    p = p->next->next;
    q = q->next;
    if (p == q) return true;
}
return false;

160 两链表是否有公共节点

if (!headA || !headB) {
    return NULL;
}
ListNode *p = headA, *q = headB;
// 循环交叉走同一步数,抹除长度差,肯定会到达一点
while (p != q) {
    p = p ? p->next : headB;
    q = q ? q->next : headA;
}
return p;

202 环循环重复情况

// 出现循环环重复情况,快慢指针一定追上
int p = n;
int q = n;
do {
    p = squareSum(p);
    q = squareSum(q);
    q = squareSum(q);
} while (p != q);
return p == 1;

203 移除链表指定元素,返回链表头

// 实际情况下要考虑内存泄漏条件
ListNode* removeElements(ListNode* head, int val) {
	// 递归结束条件
	if (!head) return head;
	head->next = removeElements(head->next, val);
	return head->val == val ? head->next : head;
}
// 可通过创建辅助头节点和 p->next 迭代进行删除
if (!head) return head;
ListNode *first = new ListNode();
first->next = head;
ListNode *p = first;
while (p->next) {
	if (p->next->val == val) {
		ListNode *del = p->next;
		p->next = p->next->next;
		delete del;
	} else {
		p = p->next;
	}
}
return first->next;

206 反转链表

// 迭代思路
ListNode *pre = NULL;
ListNode *cur = head;
while (cur) {
    ListNode *next = cur->next;
    cur->next = pre;
    pre = cur;
    cur = next;
}
return pre;

234 判断回文链表

// 先通过快慢指针找到中点
ListNode *p = head;
ListNode *q = head;
while (q) {
	p = p->next;
	q = q->next ? q->next->next : q->next;
}
// 逆转后部分
ListNode *pre = NULL;
while (p) {
	ListNode *next = p->next;
	p->next = pre;
	pre = p;
	p = next;
}
// 检查
while (head && pre) {
	if (head->val != pre->val) return false;
	head = head->next;
	pre = pre->next; 
}
return true;

237 删除链表非末尾当前节点

// 删除 = 替换复制值
ListNode *del = node->next;
node->val = node->next->val;
node->next = node->next->next;
delete del;

栈和队列

232 用栈实现队列

void pop() {
	// 通过两个栈
	// 一个栈接收原栈的输出,输出原栈底,再反推回原栈
	// peek 类似
}

225 用队列实现栈

void push() {
	// 在 push 阶段模拟维护
	// 后进先出,到头部
    que.push(x);
    for (int i = 1; i < que.size(); ++i) {
        que.push(que.front());
        que.pop();
    }
}

20 括号匹配,栈

42 接水问题,单调栈

// 找左右两边的最大值中小的,减去自身高度
// 栈解,维护单调栈的性质
int ans = 0, cur = 0;
stack<int> sta;
while (cur < height.size()) {
	// 单调性质被破坏,可以接住雨水
	while (!sta.empty() && height[cur] > height[sta.top()]) {
		// 接水形成横向长方体形,top 是底部
		int top = sta.top(); sta.pop();
		if (sta.empty()) break; // 特殊情况一边接不住退出
		// 长
		int d = cur - sta.top() - 1;
		int h = min(height[cur], height[sta.top()]) - height[top];
		ans += d * h;
	}
	sta.push(cur++);
}
return ans;

// 双指针解法
int left = 0, right = height.size() - 1;
int ans = 0;
int left_max = 0, right_max = 0;
while (left < right) {
    if (height[left] < height[right]) {
    	// 如果左边如果单调高度低了,强制结算一次体积,竖方向结算
        height[left] >= left_max ? (left_max = height[left]) : ans += (left_max - height[left]);
        ++left;
    } else {
        height[right] >= right_max ? (right_max = height[right]) : ans += (right_max - height[right]);
        --right;
    }
}
return ans;

84 柱状图最大矩形面积

// 对每个能包住那块板计算最大面积,找左右小于本块高度的下标
// 利用单调栈的次数减少遍历次数
int n = heights.size();
stack<int> sta;
vector<int> left(n), right(n, n);
for (int i = 0; i < n; ++i) {
    while (!sta.empty() && heights[sta.top()] >= heights[i]) {
        right[sta.top()] = i; // 单调栈往前查,更新包住这块板之前右边最高的
        sta.pop(); 
    }
    left[i] = sta.empty() ? -1 : sta.top(); // 特殊情况考虑
    sta.push(i);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
    ans = max(ans, (right[i] - left[i] - 1) * heights[i]);
}
return ans;

221

239

树与二叉树

100 101 102 104 107 110 112 111 226 235 257 297

排序和查找

1

3

4

21

35

38

88

217

219

278

349

350

374

378

堆和优先队列

703

295

313

23

264

并查集

128

130

200

547

684

685

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值