南昌大学计专机试练习题(二)
11.数组中重复的数字
题目描述:在一个长度为n的数组里的所有数字都在0到n-1的范围内,不考虑溢出情况。数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。如[2,3,1,0,2,5,3],输出2
思路:方法1,由于数字在0-n-1范围内,可以将数组元素映射到以该数组元素为下标的数组中(通过加上数组长度n来实现),如果有某个元素值大于或等于数组长度n,说明该值已被访问。
方法2,排序之后再查找相邻元素是否相等。
方法3,利用一个辅助数组,遍历原数组时,将数组元素作为辅助数组下标,标记辅助数组。
public static int getFirstRepeatElement(int a[]) { // +n解法 将第i个元素是否访问的标志映射到第a[i]个元素上
for (int i = 0 ; i < a.length ; i++) {
int index = a[i] >= a.length ? a[i] - a.length : a[i]; // 取a[i]原来的值
if (a[index] >= a.length) return index; // 访问过
a[index] += a.length; // 未访问过加数组长度
}
return -1;
}
public static int getFirstRepeatElement2(int a[]) { // 排序解法
Arrays.sort(a);
for (int i = 0 ; i < a.length - 1 ; i++) {
if (a[i] == a[i+1]) return a[i];
}
return -1;
}
public static int getFirstRepeatElement3(int a[]) { // 辅助数组标志解法
int b[] = new int[a.length];
for (int i = 0 ; i < a.length ; i++) {
if (b[a[i]] != 1) {
b[a[i]] = 1;
} else return a[i];
}
return -1;
}
12.三数之和
题目描述:给定一个包含n个整数的数组nums。判断nums中是否存在三个元素a,b,c使得a+b+c=0?找出所有满足条件且不重复的三元组。如nums=[-1,0,1,2,-1,-4],返回[[-1,0,1],[-1,-1,2],
思路:基本思路是先排序然后选中三元组的一个元素,然后枚举其他两个元素。方法1是选中中间一个数,然后向左右枚举,满足条件则添加到列表里面,最后通过set去重。方法2同样是选中中间一个数,然后向左右枚举,在遍历的时候去重,需考虑连续三个及以上零的情况。方式三,选中第一个数,然后枚举第二和第三个数,遍历时去重。
public static Set<String> getThreeSum(int a[]) { // 遍历a[i],把a[i]当做三个数的中间数,然后向左右逐渐枚举。最后放入set去重。
if (a == null || a.length < 3) return new HashSet();
Arrays.sort(a);
List<List<Integer>> lists = new ArrayList<List<Integer>>();
for (int i = 1 ; i < a.length - 1 ; i++) {
int left = i - 1, right = i + 1;
while (left >= 0 && right <= a.length - 1) {
int sum = a[left] + a[i] + a[right];
if (sum == 0) {
List<Integer> list = new ArrayList<Integer>();
list.add(a[left]);list.add(a[i]);list.add(a[right]);
lists.add(list);
left--;
right++;
} else if (sum < 0) {
right++;
}else left--;
}
}
Set<String> st = new HashSet<String>();
for (List list : lists) {
st.add(list.toString());
}
return st;
}
public static List<List<Integer>> getThreeSum2(int a[]) { // 遍历a[i],把a[i]当做三个数的中间数,然后向左右逐渐枚举。遍历时去重。
if (a == null || a.length < 3) return new ArrayList<List<Integer>>();
Arrays.sort(a);
List<List<Integer>> lists = new ArrayList<List<Integer>>();
for (int i = 1 ; i < a.length - 1 ; i++) {
if (a[i] == a[i+1]) continue; // 去重
if (i >=2 && a[i] == a[i-1] && a[i] == a[i-2] && a[i] == 0) { // 特殊元素0
List<Integer> list = new ArrayList<Integer>();
list.add(0);list.add(0);list.add(0);
lists.add(list);
}
int left = i - 1, right = i + 1;
while (left >= 0 && right <= a.length - 1) {
int sum = a[left] + a[i] + a[right];
if (sum == 0) {
List<Integer> list = new ArrayList<Integer>();
list.add(a[left]);list.add(a[i]);list.add(a[right]);
lists.add(list);
left--;
right++;
} else if (sum < 0) {
right++;
}else left--;
}
}
return lists;
}
public static List<List<Integer>> getThreeSum3(int a[]) { // 遍历a[i],把a[i]当做三个数的第一个元素,然后对第二第三个元素逐渐枚举。遍历时去重。
if (a == null || a.length < 3) return new ArrayList<List<Integer>>();
Arrays.sort(a);
List<List<Integer>> lists = new ArrayList<List<Integer>>();
int first;
for (int i = 1 ; i < a.length - 1 ; i++) {
if (i > 0 && a[i] == a[i - 1]) continue; // 第一元素去重
if ((first = a[i]) > 0) break; // 第一个元素大于零,其后的所有组合都会使结果大于0
int mid = i + 1, right = a.length - 1;
while (mid < right) {
if (first + a[mid] + a[right] < 0) mid++; // 数小,则中间指针右移,使结果变大
else if (first + a[mid] + a[right] > 0) right--; // 数大,右边指针左移,使结果变小
else {
lists.add(Arrays.asList(first, a[mid], a[right]));
mid++;right--;
while (mid < right && a[mid] == a[mid - 1]) mid++; // 第二元素去重
while (mid < right && a[right] == a[right + 1]) right--; // 第三元素去重
}
}
}
return lists;
}
13.验证回文字符串
题目描述:给定一个非空字符串s,最多删除一个字符。判断是否能成为回文字符串。如"aba",返回true,“abca”,返回true,可删除c。
思路:双指针验证,然后当i和j所在字符不想等时,分别“删除i”(i+1)和“删除j”(j-1)返回它们的或值。
public static boolean isPalindrome(String s) {
int i = 0, j = s.length() - 1;
while (i < j) {
if (s.charAt(i) != s.charAt(j)) {
return (isValid(s, i + 1, j) || isValid(s, i, j - 1));
}
i++;j--;
}
return true;
}
public static boolean isValid(String s, int i, int j) {
while (i < j) {
if (s.charAt(i) != s.charAt(j)) {
return false;
}
i++;j--;
}
return true;
}
14.Z字形变换
题目描述:讲一个给定字符串根据给定的行数,以从上往下、从左到右进行Z字形排列。比如输入"LEETCODEISHIRING",输出:
L C I R
E T O E S I I G
E D H N
最后输出需要从左往右逐行读取,产生一个新的字符串,即:“LCIRETOESIIGEDHN”。
思路:类“电梯”遍历。将每层的字符放入对应层的集合中,详见代码及注释。
public static String printZ(String s, int n) {
if (n <= 1 || n > s.length()) return s;
List<List<String>> lists = new ArrayList<List<String>>(n); // 初始化
for (int i = 0 ; i < n ; i++) lists.add(new ArrayList<String>());
boolean flag = true; // 上下移动方向
int j = 0;
for (int i = 0 ; i < s.length() ; i++) { // 将原字符串的字符按电梯轮换顺序分配给二维list
if (j == 0) flag = true; // 开始向下
if (j == n - 1) flag = false; // 开始向上
lists.get(j).add(String.valueOf(s.charAt(i)));
if (flag) j++;
else j--;
}
String ans = "";
for (int i = 0 ; i < lists.size() ; i++) {
for (j = 0 ; j < lists.get(i).size() ; j++) {
ans += lists.get(i).get(j);
System.out.print(lists.get(i).get(j)); // 输出元素
if (i == 0 || i == lists.size() - 1) { // 第一行和最后一行中元素间隔空格
int blank = 2 * (lists.size() - 1) - 1;
while (blank > 0) {
System.out.print(" ");
blank--;
}
} else { // 其他情况下的间隔
if (j % 2 == 0) {
int blank = 2 * (lists.size() - 1) - 1 - (2 * i);
while (blank > 0) {
System.out.print(" ");
blank--;
}
} else {
int blank = 2 * i - 1;
while (blank > 0) {
System.out.print(" ");
blank--;
}
}
} // 间隔
}
System.out.println();
}
return ans;
}
15.设计一个getMin的栈
题目描述:设计一个特殊的栈,在实现栈的基本功能的情况下,再实现返回栈中最小元素的操作。
思路:利用一个辅助栈,在元素入主栈时,将其与辅助栈顶元素比较,若小或等,则将元素入辅助栈,否则直接入主栈。出栈的时候,若元素与辅助栈顶元素相等,则将辅助栈顶元素一并弹出,否则直接出主栈栈顶元素。这样,getMin可通过辅助栈栈顶元素直接返回栈内最小元素。
public class MinStack {
private Stack<Integer> stack;
private Stack<Integer> stackMin;
public MinStack() {
stack = new Stack<Integer>();
stackMin = new Stack<Integer>();
}
public void push(int a) {
if (stackMin.isEmpty() || a < getMin()) stackMin.push(a); // 空或者有更小的,入小栈。
else stackMin.push(getMin()); // 保持和正常栈元素个数相同,但元素没必要保持。因为出栈时,最小栈要同步出栈。
stack.push(a);
}
public int pop() {
if (stack.isEmpty()) throw new RuntimeException("Your stack is empty.");
stackMin.pop();
return stack.pop();
}
public int getMin() {
if (stackMin.isEmpty()) throw new RuntimeException("Your stack is empty.");
return stackMin.peek();
}
public static void main(String[] args) {
MinStack minStack = new MinStack();
minStack.push(0);
minStack.push(5);
minStack.push(-8);
minStack.pop();
System.out.println(minStack.getMin());
}
}
16.用栈解决汉诺塔问题
题目描述:用栈而非常规的递归操作去解决。
思路:。。。
public static enum Action{
No, LToM, MToL, MToR, RToM
}
public static int fStackToStack(Action [] record, Action preNoAct, Action nowAct, Stack<Integer> fStack, Stack<Integer> tStack, String from, String to) {
if (record[0] != preNoAct && fStack.peek() < tStack.peek()) {
tStack.push(fStack.pop());
System.out.println("Move " + tStack.peek() + " from " + from + " to " + to);
record[0] = nowAct;
return 1;
}
return 0;
}
public static int hanoi2(int n, String left, String mid, String right) {
Stack<Integer> sl = new Stack<Integer>(); sl.push(Integer.MAX_VALUE);
Stack<Integer> sm = new Stack<Integer>(); sm.push(Integer.MAX_VALUE);
Stack<Integer> sr = new Stack<Integer>(); sr.push(Integer.MAX_VALUE);
for (int i = n ; i > 0 ; i--) sl.push(i);
Action[] record = {Action.No};
int step = 0;
while (sr.size() != n + 1) { // 穷举所有情况直至第三个栈满
step += fStackToStack(record, Action.MToL, Action.LToM, sl, sm, left, mid);
step += fStackToStack(record, Action.LToM, Action.MToL, sm, sl, mid, left);
step += fStackToStack(record, Action.RToM, Action.MToR, sm, sr, mid, right);
step += fStackToStack(record, Action.MToR, Action.RToM, sr, sm, right, mid);
}
return step;
}
17.判断二叉树是否为平衡二叉树
题目描述:给定一棵二叉树的头结点head,判断这颗二叉树是否为平衡二叉树。
思路:后序递归遍历求树高的过程,若有左子树与右子树高度差超过1的情况,标记结果为false并返回,否则最终结果返回true。
public class BalanceBiTree {
public static int TreeDepth2(BiTree root, boolean a[]) {
if (root == null) return 0;
int left = TreeDepth2(root.left, a);
if (!a[0]) return left + 1;
int right = TreeDepth2(root.right, a);
if (!a[0]) return right + 1;
int diff = Math.abs(left - right);
if (diff > 1) a[0] = false;
return left > right ? left + 1 : right + 1;
}
public static boolean isBalanceBiTree2(BiTree root) {
boolean a[] = new boolean[1];
a[0] = true;
TreeDepth2(root, a);
return a[0];
}
public static void main(String[] args) {
BiTree root = new BiTree(1);
BiTree node1 = new BiTree(2);
BiTree node2 = new BiTree(3);
BiTree node3 = new BiTree(4);
BiTree node4 = new BiTree(5);
BiTree node5 = new BiTree(6);
root.left = node1;
root.right = node2;
node1.left = node3;
node1.right = node4;
node3.left = node5;
System.out.println(isBalanceBiTree(root));
}
static class BiTree{
BiTree left;
int data;
BiTree right;
public BiTree(int data) {
this.data = data;
}
}
}
18.二叉树的中序遍历
题目描述:给定一个二叉树,返回它的中序遍历。
思路:方法1,递归解法。方法2,非递归解法。
public static void Inorder(BiTree root) { // 递归解法
if (root == null) return;
Inorder(root.left);
System.out.print(root.data + " ");
Inorder(root.right);
}
public static void Inorder2(BiTree root) { // 非递归解法
if (root == null) return;
Stack<BiTree> s = new Stack<BiTree>();
BiTree p = root;
while (p != null || !s.isEmpty()) {
if (p != null) {
s.push(p);
p = p.left;
} else {
BiTree b = s.pop();
System.out.print(b.data + " ");
p = p.right;
}
}
}
19.只出现一次的数字
题目描述:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现一次的元素。
思路:方法1,排序。方法2,位运算之异或运算。异或有个性质就是相同的两个数异或结果为0,0与非0异或为非零,因此,可将数组中的数累次进行异或,最终结果就是那个只出现一次的元素(注:异或满足交换律)。
public static int getOneNum(int a[]) { // 排序解法
Arrays.sort(a);
for (int i = 0 ; i < a.length - 2 ; i += 2) {
if (a[i] != a[i+1]) return a[i];
}
return 0;
}
public static int getOneNum2(int a[]) { // 位运算 异或
int ans = 0;
for (int i = 0 ; i < a.length ; i++) {
ans ^= a[i];
}
return ans;
}
20.扩展“只出现一次的数字”
题目描述:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
思路:方法1,排序。
方法2,位运算(方向同样是消掉3次重复的数据)。考虑到一个int型数据是32位,对数组中每个元素的每一个“位”进行统计(统计1的次数),那么出现三次的元素各“位”统计下来必定是0或者3,所有出现三次的数字各“位”统计下来必定是3的倍数。比如说[10,10,8,10,2,2,2],(10)(10)=(0000 0000 0000 0000 0000 0000 0000 1010)(2),我们从第0-31位分别统计每位1出现的次数,这里10出现三次,那么低位第2位1出现的次数就为三,然后2也出现三次,低2位贡献3个1,而8出现一次,低二位不贡献1,因此1的总个数模三后就是8对应位1的个数。这里注意统计时右移了i位,因此求返回结果时需要还原即左移i位,进行累次或运算。
方法3,位运算(方向是设计一个逻辑运算,类似异或运算,但与之不同,该运算是三次相同则消零),但与法2不同。法2是将各位的1统计出来,而这里是将各位的运算结果通过两个位(高位a,低位b)来存储起来。且这两个位是逢3消为0,即00->01->10->00。先写真值表:
a输入 | b输入 | 输入i | a输出 | b输出 |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
0 | 0 | 1 | 0 | 1 |
0 | 1 | 1 | 1 | 0 |
1 | 0 | 1 | 0 | 0 |
可推导出b和a的逻辑表达式:bout=~ ain & (bin ^ iin);aout=~ bout & (ain ^ iin)。
public static int getOneNum(int a[]) { // 排序解法
Arrays.sort(a);
for (int i = 0 ; i < a.length - 3 ; i += 3) {
if (a[i] != a[i+1] || a[i] != a[i+2]) return a[i];
}
return a[a.length - 1];
}
public static int getOneNum2(int a[]) { // 位运算 通用解法O(32N),可对n=3,4,5,6...适用
int ans = 0;
for (int i = 0 ; i < 32 ; i++) {
int count = 0;
for (int j : a) { // 统计每一位1的个数
count += j >> i & 1;
}
ans |= count % 3 << i;
}
return ans;
}
public static int getOneNum3(int a[]) { // 位运算 2位表示1的个数,00 01 10 00第三个1会清零O(N)
int high = 0, low = 0;
for (int i : a) {
low = ~high & (low ^ i); // 可根据真值表推出逻辑表达式
high = ~low & (high ^ i);
}
return low;
}
21.扩展“只出现一次的数字”
题目描述:一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。如输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]。(来源:LeetCode面试题56-1)
思路:和19不同,这里要求出两个出现一次的数字,这里关键在于利用异或的性质,先将数组所有数异或出来,结果会得到两个出现一次的数的异或。考虑二进制,这两个数异或出来的如果是1,说明对应位不同;如果是0,说明对应位相同。因此我们可以找出异或结果右边开始第一个为1的位置,然后将原来的两个数分成两组,这样就可以分别求出题目所要求的两个数。
public int[] singleNumbers(int[] nums) {
int ans[] = new int[2];
int a = 0;
for (int i = 0 ; i < nums.length ; ++i){ // 所有数的异或
a ^= nums[i];
}
int one = 0;
for (int i = 0 ; i < 32 ; ++i, ++one){ // 从右边开始找a的第一个为1的位置
if (((a >> one) & 1) == 1) break;
}
for (int i = 0 ; i < nums.length ; ++i){ // 分组求解
if (((nums[i] >> one) & 1) == 1) ans[0] ^= nums[i];
else ans[1] ^= nums[i];
}
return ans;
}