笔试、手撕代码与八股文实战题

笔试、手撕代码与八股文实战题

  • 前言
  • 一、笔试
    • 1.输入输出
    • 2.根据换行符判断输入结束
    • 3.跳跃游戏
    • 4.三数之和
    • 5.港口运货最小承载量
    • 5.旅游线路
  • 二、手撕代码
    • 1.不含/含重复元素的全排列
    • 2.二分查找第一个小于target的下标
    • 3.有障碍物从左上到右下的最短距离
    • 4.移除链表中的重复节点
    • 5.判断给定字符串的最小回文子串
    • 6.用宏定义写出swap(x,y)
    • 7.最小覆盖子串
    • 7.三数之和
    • 7.盛水最多的容器
  • 三、八股文
    • 1.面向对象特性
    • 2.深拷贝与浅拷贝
    • 3.move的作用
    • 4.堆和栈的区别,栈溢出的解决方案
      • 4.1 堆栈区别
      • 4.2 栈溢出原因
      • 4.3 栈溢出解决方案


前言

记录朋友们与我遇到的手撕代码题和实战题。


一、笔试

1.输入输出

读取数字。

int a;
float b;
scanf("%d %f", &a, &b);
//
cin >> a >> b;

读取带空格的字符串。

string s;
getline(cin, s);

读取直到换行符之前的值。

vector<int> v;
while (cin.peek() != '\n') {
	int t;
	cin >> t;
	v.emplace_back(move(t));
}

输出printf或者cout

printf("outoput : %d.\n", a);
cout << a;

2.根据换行符判断输入结束

使用cin.peek()函数

while (cin.peek() != '\n') {
    int tmp;
    cin >> tmp;
    nums.push_back(tmp);
}
cin.ignore();

或者这种方式也可以,但是我用vscode不好用。

while (1) {
	int tmp;
	cin >> tmp;
	nums.push_back(tmp);
	while (getchar() == '\n') break;
}

3.跳跃游戏

bool jump(vector<int>& nums) {
	int n = nums.size();
	int step = 0;
	int maxP - 0; // 下一step能够到达最远的距离
	int end = 0; // 每一step能够到达最远的距离
	for (int i = 0; i < n; ++i) {
		mP = min(n - 1, max(mP, i + nums[i]));
		if (i == 0) end = mP;
		else if (i == end) {
			end = mP;
			step++;
		}
	}
	return step;
}

4.三数之和

	vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int n = nums.size();
        vector<vector<int>> ans;
        for (int i = 0; i < n; ++i) {
            while (i > 0 && i <= n - 2 && nums[i] == nums[i - 1]) i++;
            auto result = twosum(nums, i + 1, n - 1, -nums[i]);
            ans.insert(ans.end(), result.begin(), result.end());
        }
        return ans;
    }
    vector<vector<int>> twosum(vector<int>& nums, int start, int end, int target) {
        vector<vector<int>> ans;
        if (end <= start) return ans;
        int sum = 0;
        while (start < end) {
            sum = nums[start] + nums[end];
            if (sum == target) {
                vector<int> tmp;
                tmp.push_back(-target);
                tmp.push_back(nums[start]);
                tmp.push_back(nums[end]);
                ans.push_back(tmp);
                while (start < end && nums[start] == nums[start + 1]) start++;
                start++;
                while (start < end && nums[end] == nums[end - 1]) end--;
                end--;
            } else if (sum < target) start++;
            else end--;
        }
        return ans;
    }

5.港口运货最小承载量

    int shipWithinDays(vector<int>& weights, int days) {
        int n = weights.size(), r = 0, l = 0, m = 0;
        for (int i = 0; i < n; ++i) {
            r += weights[i];
            l = max(l, weights[i]);
        }
        while (l <= r) {
            m = (l + r) / 2;
            int need = 1, cur = 0;
            for (int w : weights) {
                if (cur + w > m) {
                    need++;
                    cur = w;
                } else cur += w;
            }
            if (need > days) l = m + 1;
            else r = m - 1;
        }
        return l;
    }

5.旅游线路

给出M条旅游线路,一行给出M个线路的起始日期,另一行给出M个线路的终止日期。输出有多少种满足有3条旅游地的方案。
比如4条线路,分别为:
5 1 3 2
7 2 4 4
输出为1,因为只有一条线路满足3个旅游地:
[1,2] - [3,4] - [5,7]
这一题是明显的回溯算法题,通过不断增加新的旅游地,判断是否满足各个旅游地起始终止日期不发生重叠。

二、手撕代码

1.不含/含重复元素的全排列

原题是包含重复元素的实战题。我先从不含重复元素的开始整理。

void getans(vector<int>& nums, vector<vector<int>>& ans, const int len, int s1){
    if (s1 == len) {
        for (const int i: nums) cout << i << " ";
        cout << endl;
        ans.emplace_back(nums);
        return;
    }
    for (int i = s1; i < len; ++i) {
        swap(nums[i], nums[s1]);
        getans(nums, ans, len, s1 + 1);
        swap(nums[i], nums[s1]);
    }
}

vector<vector<int>> permute(vector<int>& nums) {
    int n = nums.size();
    if (n == 1) return vector<vector<int>>{nums};
    vector<vector<int>> ans;
    getans(nums, ans, n, 0);
    return ans;
}

如果是含重复元素的全排列问题,在排序后应该会处于相邻的位置,此时只需要判断相邻是否一样即可。

void getans(vector<int>& nums, vector<vector<int>>& ans, const int len, int s1){
    if (s1 == len) {
        for (const int i: nums) cout << i << " ";
        cout << endl;
        ans.emplace_back(nums);
        return;
    }
    set<int> s;
    for (int i = s1; i < len; ++i) {
    	if (i > s1 && nums[s1] == nums[i]) continue; // 避免相邻的相同
    	if (s.find(nums[i]) != s.end()) continue; // 避免待交换的相同
    	s.insert(nums[i]); // 避免以后出现相同的
    	swap(nums[s1], nums[i]);
    	getans(nums, ans, len, s1 + 1);
    	swap(nums[s1], nums[i]);
    }
}

我在集度面试的时候也遇到了,而且额外要求输出是字典序,所以需要再sort一遍。现整理如下。

class Solution {
private:
    vector<vector<int> > ans;
public:
    vector<vector<int> > permuteUnique(vector<int> &num) {
        sort(num.begin(), num.end());
        int n = num.size();
        backtracking(num, 0, n);
        sort(ans.begin(), ans.end());
        return ans;
    }
    void backtracking(vector<int> &num, int idx, int n) {
        if (idx == n - 1) {
            ans.push_back(num);
            return;
        }
        backtracking(num, idx + 1, n);
        set<int> s;
        for (int i = idx; i < n; ++i) {
            if (num[idx] == num[i]) continue;
            if (s.find(num[i]) != s.end()) continue;
            s.insert(num[i]);
            swap(num[idx], num[i]);
            backtracking(num, idx + 1, n);
            swap(num[idx], num[i]);
        }
    }
};

2.二分查找第一个小于target的下标

void bisection(vector<int> nums, int target) {
    int l = 0, r = nums.size() - 1, mid = 0;
    while (l <= r) {
        mid = (l + r) / 2;
        if (nums[mid] >= target) r = mid - 1;
        else l = mid + 1;
    }
    cout << r << endl;
}

3.有障碍物从左上到右下的最短距离

4.移除链表中的重复节点

    ListNode* removeDuplicateNodes(ListNode* head) {
        set<int> s;
        if (!head || !head->next) return head;
        ListNode* prev = head, *cur = head->next;
        s.insert(head->val);
        while (cur) {
            if (s.find(cur->val) != s.end()) {
                prev->next = cur->next;
                cur = cur->next;
                continue;
            }
            s.insert(cur->val);
            prev = cur;
            cur = cur->next;
        }
        return head;

    }

5.判断给定字符串的最小回文子串

#include 
#include 
using namespace std;

int main() {
 string s = "dddbccbad";
 int n = s.size(), l = 0, len = 0;
 vector<vector<bool>> dp(n, vector<bool>(n, false));
 for (int i = 0; i < n; ++i) {
    for (int j = 0; j < n - i; ++j) {
        if (i == 0) dp[j][j] = true;
        else {
            if (s[i + j] == s[j] && (i == 1 || dp[j + 1][i + j - 1])) {
                dp[j][i + j] = true;
                if (i > len) {
                    l = j;
                    len = i + 1;
                }
            }
        }
    }
}
 cout << s.substr(l, len);
 return 0;
}

6.用宏定义写出swap(x,y)

#define swap(x, y) do{x=x+y;y=x-y;x=x-y;}while(0)

7.最小覆盖子串

这是朋友面试遇到的题目,是leetcode第76题,属于hard,特此记录一下。

string minWindow(string s, string t) {
	// count用来记录位数,之后遍历的时候遇到一个而且需要的话就减一个
	// l r 双指针
	// minl minlen 记录最短的下标起始和长度
	// 注意minlen初始化长度为一个很大的值,是因为有可能没有满足要求的子串,通过这个较大值来判断是否有
	int count = t.size(), l = 0, r = 0, minl = 0, minlen = s.size() + 1;
	vector<bool> exist(128, false); // 用来记录t中某个字母存不存在,也可以用哈希表来找
	vector<int> need(128, 0); // 用来记录t中字母出现的次数,因为可能重复
	for (char c : t) {
		need[t]++;
		exist[t] = true;
	}
	for (;r < s.size(); ++r) { // 开始双指针遍历
		if (exist[s[r]]) {
			if (need[s[r]] > 0) count--; // 找到一个需要的字母
			need[s[r]]--; // 不管需不需要,都提供了
			while (count == 0) {
				if (minlen > r - l + 1) { // 更新最短字符串
					minl = l;
					minlen = r - l + 1;
				}
				// 开始移动左指针
				if (exist[s[l]]) need[s[l]]++; // 提供的少了1个
				if (need[s[l]] > 0) count++; // 如果把l对应的字母拿掉,count就得+1个
				l++;
			}
		}
	}
	if (minlen == s.size() + 1) return ""; // 每找到,返回空字符串
	return s.substr(minl, minlen);
}

7.三数之和

两数之和的升级版。

vector<vector<int>> threeSum(vector<int>& nums) {
	int n = nums.size();
	vector<vector<int>> ans;
	if (n < 3) return ans;
	for (int i = 0; i < n - 2; ++i) { // 固定住第1位
		if (i > 0 && nums[i] == nums[i - 1]) continue;
		int target = -nums[i], r = n - 1;
		for (int l = i + 1; l < n - 1; ++l) { // 固定住第2位
			if (l > i + 1 && nums[l] == nums[l - 1]) continue;
			while (l < r && nums[l] + nums[r] > target) r--; // 因为第2位固定住了,所以只能移动第3位
			if (l == r) continue; // 有可能r退到l处了
			if (nums[l] + nums[r] == target) ans.push_back({nums[i], nums[l], nums[r]});
		}
	}
	return ans;
}

7.盛水最多的容器

Leetcode第11题。思想是双指针,总是移动矮的那根。

    int maxArea(vector<int>& height) {
        int n = height.size(), l = 0, r = n - 1, ans = (n - 1) * min(height[l], height[r]);
        while (l < r) {
            ans = height[l] < height[r] ? 
            max(ans, min(height[++l], height[r]) * (r - l)) : max(ans, min(height[l], height[--r]) * (r - l));
        }
        return ans;
    }

三、八股文

1.面向对象特性

封装,继承,多态。

2.深拷贝与浅拷贝

封装,继承,多态。

3.move的作用

move将一个左值强行转化为右值,通过右值引用使用该值,可以避免拷贝,提高效率。
常见应用场景:
1.移动构造函数
2.vector.emplace_back()

4.堆和栈的区别,栈溢出的解决方案

4.1 堆栈区别

堆内存由程序员创建和释放,栈内存由编译器自动创建和释放。
堆频繁使用会产生大量碎片,使程序效率降低;栈由于是编译器管理则不会产生这个问题。
栈的内存地址向减小方向使用;堆的内存地址向增大方向使用。

4.2 栈溢出原因

函数调用层数过多,每调用一次,函数的参数、局部变量等信息就压一次栈;
局部静态变量体积过大。

4.3 栈溢出解决方案

一是增大栈空间;二是改用动态分配,使用堆(heap)而不是栈(stack)。

你可能感兴趣的:(算法,数据结构)