1.二进制求和(67-易)
给你两个二进制字符串,返回它们的和(用二进制表示)。输入为 非空 字符串且只包含数字 1
和 0
。
示例:
输入: a = "11", b = "1" 输出: "100"
思路:
- 使用StringBuilder进行字符串拼接,最后结果不要忘记进行反转
- 对当前位a、b加和sum取余和取模分别计算当前位的拼接值和进位值carry
- 都遍历结束注意判断是否存在进位值
代码:
public String addBinary(String a, String b) { StringBuilder ans = new StringBuilder(); int carry = 0; for (int i = a.length() - 1, j = b.length() - 1; i >= 0 || j >= 0; i--, j--) { int sum = carry; sum += i >= 0 ? a.charAt(i) - '0' : 0; sum += j >= 0 ? b.charAt(j) - '0' : 0; ans.append(sum % 2); carry = sum / 2; } ans.append(carry == 1 ? carry : ""); return ans.reverse().toString(); }
2.加一(66 - 易)
给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外,这个整数不会以零开头。
示例:
输入:digits = [1,2,3] 输出:[1,2,4] 解释:输入数组表示数字 123。
思路:注意进位分为下边三种情况:
- 末位无进位,则末位加一即可:因为末位无进位,前面也不可能产生进位,比如 45 => 46
- 末位有进位,但在中间位置进位停止:则需要找到进位的典型标志,即为当前位 %10后为 0,则前一位加 1,直到不为 0 为止,比如 499 => 500
- 末位有进位,并且一直进位到最前方导致结果多出一位:对于这种情况,需要在第 2 种情况遍历结束的基础上,进行单独处理,比如 999 => 1000
ps:模拟加法应该进行倒序遍历。
代码:
public int[] plusOne(int[] digits) { int len = digits.length; for(int i = len - 1; i >= 0; i--) { digits[i]++; digits[i] %= 10; if(digits[i]!=0) return digits; } digits = new int[len + 1]; digits[0] = 1; return digits; }
3.单链表加1(369-中)
题目描述:用一个 非空 单链表来表示一个非负整数,然后将这个整数加一。 你可以假设这个整数除了 0 本身,没有任何前导的 0。 这个整数的各个数位按照 高位在链表头部、低位在链表尾部 的顺序排列。
示例:
输入: [1,2,3] 输出: [1,2,4]
思路:****类似数组+1,注意反转链表,一般有三种情况:
- 尾部不为9,直接+ 1;
- 尾部为9,进位
- 全部为9,需要新建一个链表头
还有一种快慢指针(双指针)的解法,比较巧妙:
- fast.val != 9,慢指针移动到当前快指针处
- fast.val = 9,慢指针原地不动
- 遍历结束,慢指针的值加一,慢指针后续所有节点值设置为0!
代码:
public class Solution003 { /** * 链表加一,正向输出 */ public static class ListNode { int val; ListNode next; public ListNode() { } public ListNode(int val) { this.val = val; } public ListNode(int val, ListNode next) { this.val = val; this.next = next; } } public static void main(String[] args) { // 测试 1->4->3->2->NULL ListNode node = new ListNode(9); node.next = new ListNode(9); // ListNode head = plusOne(node); ListNode head = plusOne_1(node); while (head != null) { System.out.print(head.val); head = head.next; } } public static ListNode plusOne(ListNode head) { ListNode reverseHead = reverse(head); ListNode cur = reverseHead; ListNode lastNode = null; int add = 0, c = 1; while (cur != null) { int sum = cur.val + add + c; add = c = 0; if (sum == 10) { cur.val = 0; add = 1; } else { cur.val = sum; } if (cur.next == null) { lastNode = cur; } cur = cur.next; } // 特判,需要新建头结点的情况 if (add > 0) { lastNode.next = new ListNode(add); } return reverse(reverseHead); } public static ListNode plusOne_1(ListNode head) { if (head == null) { return null; } ListNode slow = new ListNode(0); ListNode fast = head; slow.next = fast; while (fast != null) { if (fast.val != 9) { slow = fast; } fast = fast.next; } slow.val += 1; ListNode cur = slow.next; while (cur != null) { cur.val = 0; cur = cur.next; } return slow.next == head ? slow : head; } public static ListNode reverse(ListNode head) { if (head == null) { return null; } ListNode pre = null, next; while (head != null) { next = head.next; head.next = pre; pre = head; head = next; } return pre; } }
4.链表求和(面试题 02.05)
给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表形式返回结果。链表不能修改
进阶:思考一下,假设这些数位是正向存放的,又该如何解决呢?
示例:
输入:(7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295 输出:2 -> 1 -> 9,即912 进阶: 输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) 即7243 + 564 输出:7 -> 8 -> 0 -> 7 即7807
思路:对于原问题,常规解法,注意组织结果链表使用虚拟节点(结果要求逆序)。
进阶问题:
- 使用两个栈,倒序的取出链表的值
- 使用头插法组织新链表!(结果要求正序)
代码:原问题
public ListNode addTwoNumbers(ListNode l1, ListNode l2) { int sum, carry = 0; ListNode dummyHead = new ListNode(0); ListNode pre = dummyHead; while (l1 != null || l2 != null || carry > 0) { sum = carry; sum += l1 != null ? l1.val : 0; sum += l2 != null ? l2.val : 0; carry = sum / 10; pre.next = new ListNode(sum % 10); pre = pre.next; l1 = l1 != null ? l1.next : l1; l2 = l2 != null ? l2.next : l2; } return dummyHead.next; }
进阶问题:
public ListNode addTwoNumbers(ListNode l1, ListNode l2) { Stack<Integer> stack1 = new Stack<>(); Stack<Integer> stack2 = new Stack<>(); while (l1 != null) { stack1.push(l1.val); l1 = l1.next; } while (l2 != null) { stack2.push(l2.val); l2 = l2.next; } int carry = 0; ListNode head = null; while (!stack1.isEmpty() || !stack2.isEmpty() || carry > 0) { int sum = carry; sum += (stack1.isEmpty() ) ? 0 : stack1.pop(); sum += (stack2.isEmpty() ) ? 0 : stack2.pop(); carry = sum / 10; ListNode node = new ListNode(sum % 10); // 头插节点构造链表 node.next = head; head = node; } return head; }
5.面试题 17.01. 不用加号的加法
设计一个函数把两个数字相加。不得使用 + 或者其他算术运算符。
思路:本题不能使用运算符进行求解,考察点是位运算:
- 运算:相同为0,不同为1。两个数异或,**ab 计算不考虑进位的二进制加法结果**
- &运算:两个都是1与的结果为1,否则为0。a&b 计算所有进位的位,左移(<<)再异或就是进一位的结果,以此类推
代码:
public int add(int a, int b) { int tmp; while (b != 0) { tmp = a ^ b; b = (a & b) << 1; a = tmp; } return a; }
6.字符串相加(415 - 易)
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。不能使用库函数等。
思路:与上面类似,模拟竖向加法,注意:StringBuilder可以显著提高效率。
代码:
public String addStrings(String num1, String num2) { StringBuilder ans = new StringBuilder(""); int i = num1.length() - 1, j = num2.length() - 1; int carry = 0, sum; while (i >= 0 || j >= 0 || carry != 0) { sum = carry; sum += i >= 0 ? num1.charAt(i) - '0' : 0; sum += j >= 0 ? num2.charAt(j) - '0' : 0; ans.append(sum % 10); carry = sum / 10; i--; j--; } return ans.reverse().toString(); }
6.字符串相减(面试题补充)
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的差。
注意:
- num1 和num2 都只会包含数字 0-9
- num1 和num2 都不包含任何前导零
- 你不能使用任何內建 BigInteger 库
思路:本题与上题唯一不同的是大数相减可能产生负数,分两步:
- 判断两个字符串的大小(对应大数)
- 模拟相减(根据大小),返回结果
注意细节:
- 相减过后,前导0要去除
- 对于字符串
"0"
,不需加负号
ps:compareTo()方法依次比较两个字符串的大小,不同则返回对应字符的ascii码差值,停止;当一方比较完成则比较长度。
代码:
public class Solution001 { public static void main(String[] args) { // String num1 = "121", num2 = "120"; String num1 = "119", num2 = "123"; System.out.println(num1.compareTo(num2)); System.out.println(subString(num1, num2)); } public static String subString(String num1, String num2) { String ans = null; if (isLess(num1, num2)) { ans = sub(num2, num1); if (ans != "0") { StringBuilder sb = new StringBuilder(ans); sb.insert(0, '-'); ans = sb.toString(); } } else { ans = sub(num1, num2); } return ans; } // 比较两个数的大小 public static boolean isLess(String num1, String num2) { return num1.compareTo(num2) < 0; } public static String sub(String num1, String num2) { StringBuilder sb = new StringBuilder(); int i = num1.length() - 1, j = num2.length() - 1; int borrow = 0; while (i >= 0 || j >= 0) { int x = i >= 0 ? num1.charAt(i) - '0' : 0; int y = j >= 0 ? num2.charAt(j) - '0' : 0; sb.append((x - borrow - y + 10) % 10); borrow = x - borrow - y < 0 ? 1 : 0; i--; j--; } String tmp = sb.reverse().toString(); //删除前导0,注意边界是res.size()-1!!防止当res为"0000"时,删为""的清空 int start = 0; for (int pos = 0; pos < tmp.length(); pos++) { if (tmp.charAt(pos) != '0') { start = pos; break; } } return tmp.substring(start); } }
7.36进制加法(高频面试题)
36进制由0-9,a-z,共36个字符表示。
要求按照加法规则计算出任意两个36进制正整数的和,如1b + 2x = 48 (解释:47+105=152 )要求:不允许使用先将36进制数字整体转为10进制,相加后再转回为36进制的做法
思路:本题是上一题字符串相加的延伸,本题是36进制的大数字符串相加,输出结果为36进制数。
- getChar:拼接时,将数值转换为36进制字符
- getInt:将36进制字符转换为数值
代码:
public class Solution { public static void main(String[] args) { String a = "1b", b = "2x", c; // System.out.println(getInt('z')); // System.out.println(getChar(35)); c = add36Strings(a, b); System.out.println(c); } public static String add36Strings(String num1, String num2) { StringBuilder ans = new StringBuilder(); int i = num1.length() - 1, j = num2.length() - 1; int carry = 0, sum; while (i >= 0 || j >= 0 || carry != 0) { sum = carry; sum += i >= 0 ? getInt(num1.charAt(i)) : 0; sum += j >= 0 ? getInt(num2.charAt(j)) : 0; ans.append(getChar(sum % 36)); carry = sum / 36; i--; j--; } return ans.reverse().toString(); } public static int getInt(char c) { if (c >= '0' && c <= '9') { return c - '0'; } else { return c - 'a' + 10; } } public static char getChar(int a) { if (a <= 9) { return (char)(a + '0'); } else { return (char)(a - 10 + 'a'); } } }
7.补充:字符串相乘(43 - 中)
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的乘积。不能使用库函数等。
思路:模拟惩罚的计算过程,将被乘数与乘数的没一位相乘,然后调用两个字符串相加。
参考leetcode题解,在两数相乘时,乘数某位与被乘数某位相乘,与产生结果的位置的规律来完成。具体规律如下:
- 乘数 num1 位数为 M,被乘数 num2 位数为 N, num1 x num2 结果 ans 最大总位数为 M+N
- num1[i] x num2[j] 的结果为 tmp(位数为两位,"0x","xy"的形式),其第一位位于 res[i+j],第二位位于 res[i+j+1]。
算法流程
代码:
public String multiply(String num1, String num2) { if (num1.equals("0") || num2.equals("0")) { return "0"; } int m = num1.length(), n = num2.length(); int[] arr = new int[m + n]; for (int i = m - 1; i >= 0; i--) { int n1 = num1.charAt(i) - '0'; for (int j = n - 1; j >= 0; j--) { int n2 = num2.charAt(j) - '0'; int sum = arr[i + j + 1] + n1 * n2; arr[i + j + 1] = sum % 10; arr[i + j] += sum / 10; // 记录进位值 } } StringBuilder ans = new StringBuilder(); for (int i = 0; i < m + n; i++) { if (i == 0 && arr[i] == 0) continue; ans.append(arr[i]); } return ans.toString(); }